文章目录
分库分表,能不分就不分
为什么要分库分表
数据库是一个应用中最为核心的价值,之前我们学习过很多对sql的调忧,但是互联网应用越来越大,数据库会频繁的成为整个应用的性能瓶颈,而传统的SQL调优也无法处理海量数据。
- 单库承载极限
- 当单表数据量 > 2000万行(如航空订单表),查询延迟呈指数级上升
- 示例:某航司订单表年增2000万条,全表扫描耗时从 0.1s → 12s+
- 资源竞争风暴
- 高并发场景(如机票秒杀)下:
- CPU利用率 > 90% 导致线程阻塞
- 磁盘IOPS峰值超物理限制
- 高并发场景(如机票秒杀)下:
分库分表的优势
分库分表是一种数据库架构优化技术,通过将数据分散到多个数据库或表中,以应对高并发、大数据量场景,提升系统性能和扩展性。
分库分表优点:
- 提高系统性能:分库分表可以将大型数据库分成多个小型数据库,每个小型数据库只需要处理部分数据,因此可以提高数据库的并发处理能力和查询性能
- 提高系统可用性:分库分表可以将数据复制到多个数据库中,以提高数据的可用性和可靠性。如果一个数据库崩溃了,其他他数据库可以接管其工作,以保持系统的正常运行。
- 提高系统可扩展性:分库分表可以使系统更容易扩展。当数据量增加时,只需要增加更多的数据库和表,而不是替换整个数据库,因此系统的可扩展性更高。
- 提高系统灵活性:分库分表可以根据数据的使用情况,对不同同的数据库和表进行不同的优化。例如,可以将经常使用的数据存储在性能更好的数据库中,或者将特定类型的数据存储在特定的表中以提高系统的灵活性。
应用场景:
• 数据量过大: 单表数据量超过千万,查询性能下降。
• 高并发: 单库无法承受大量读写请求。
• 扩展性需求: 支持水平扩展,动态添加数据库或表。
分库分表的挑战
可能你会说,分库分表嘛,也不是很难,一个库存不下,那就把数据拆分到多个库,一张表数据太多了,就把同一张表的数据拆分到多张。至于怎么做,也不难啊,要操作多个数据库,那么建立多个jdbc连接就行了。要写到多张表,修改下sql语句就行了。
如果你也这么觉得,那么就大错特错了,分库分表也并不是字面意义上的将数据分到多个库或者多个表这么简单,他需要的是一系列的分布式解决方案。
常见问题:
- 主键避重问题:在分库分表环境中,由于表中数据同时存在不同数据库中,某个分区数据库生成的ID就无法保证全局唯一。因此需要单独设计全局主键以避免跨库主键重复问题。
- 分布式事务问题:原本单机数据库有很好的事务机制能够帮我们保证数据一致性,但是分库分表后,由于数据分布在不同库甚至不同服务器,不可避免会带来分布式事务问间题。
- 跨库查询归并问题 :跨节点进行查询时,每个分散的数据库中只能查出一部分的数据,这时要对整体结果进行归并时,就会变得非常复杂,比如常见的分页limit、排序order by等操作。
- 数据迁移问题:当数据库集群需要进行扩缩容时,集群中的数据也需要随着服务进行迁移。如何在不影响业务稳定性的情况下进行数据居迁移也是数据库集群化后需要考虑的问题。
- SQL路由问题:数据被拆分到多个分放的数据库服务当中,每个数据库服务只能保存一部分的数据。这时,在执行SQL语句检索数据时,如问快速定位到目标数据所在的数据库服务,并将sql语句转到对应的数据库服务中执行,也是提升检索效率必须要考虑的问题。
分库分表类型
分片类型 | 描述 | 优点 | 缺点 |
---|---|---|---|
垂直分库 | 按业务模块拆分数据库(如用户库、订单库) | 业务清晰,维护简单 | 跨库事务复杂 |
水平分库 | 按分片键(如用户 ID)将数据分散到多个数据库 | 支持高并发和大数据量 | 分片算法设计复杂 |
垂直分表 | 按字段拆分表(如用户信息表、用户扩展表) | 减少单表大小,优化查询 | 增加开发复杂性 |
水平分表 | 按分片键将单表数据分散到多个表 | 单库内优化性能 | 表结构重复,维护成本高 |
ShardingJDBC常见数据分片策略
分布式主键生成方案:SNOWFLAKE / UUID / 主键初始值+步长
分片策略 = 分片键 + 分片算法
分片键:拆分表的字段,比如user表根据id进行拆分,则id就是分片键
分片算法:将数据拆分到不同真实表的算法
精确分片算法:PreciseShardingAlgorithm,使用一个分片键,处理in、=查询,配合StandardShardingStrategy策略
范围分片算法:RangeShardingAlgorithm,使用一个分片键,处理between and、>、>=、<、<=查询,,配合StandardShardingStrategy策略
复合分片算法:ComplexKeysShardingAlgorithm,使用多个分片键,配合ComplexShardingStrategy策略
hint分片算法:HintShardingAlgorithm,强制路由分片,配合HintShardingAlgorithm策略
分片策略:
标准分片策略:精确分片算法(必选,处理in、=)、范围分片算法(可选,处理范围查询),如不设置RangeShardingAlgorithm,处理范围查询时使用全库表路由
复合分片策略:使用ComplexKeysShardingAlgorithm处理多分片键
Hint分片策略:强制路由分片,使用Hint 指定分片值
行表达式分片策略:用Grooy表达式,直接在配置文件中进行分片配置,支持单分片对=、in进行处理
不分片策略:不对数据库、表进行分片处理
第一个分库分表的案例
要求:将100条数据插入到以下2个库ds0、ds1,共四张表sys_user0、sys_user1中
建表语句
sql
CREATE DATABASE ds0;
USE ds0;
DROP TABLE IF EXISTS `sys_user0`;
CREATE TABLE `sys_user0` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`update_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
`create_user` varchar(100) NOT NULL COMMENT '创建人编码',
`update_user` varchar(100) NOT NULL COMMENT '修改人编码',
`delete_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记(1 删除 0未删除)',
`pos_code` varchar(50) DEFAULT NULL COMMENT '职位编码',
`disable_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '启用标记(1 禁用 0启用)',
`avatar` varchar(100) DEFAULT NULL COMMENT '头像地址',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`dept_code` varchar(50) DEFAULT NULL COMMENT '部门编码',
`user_phone` varchar(15) DEFAULT NULL COMMENT '手机号',
`tenant` varchar(15) DEFAULT NULL COMMENT '租户',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统用户表';
DROP TABLE IF EXISTS `sys_user1`;
CREATE TABLE `sys_user1` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`update_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
`create_user` varchar(100) NOT NULL COMMENT '创建人编码',
`update_user` varchar(100) NOT NULL COMMENT '修改人编码',
`delete_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记(1 删除 0未删除)',
`pos_code` varchar(50) DEFAULT NULL COMMENT '职位编码',
`disable_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '启用标记(1 禁用 0启用)',
`avatar` varchar(100) DEFAULT NULL COMMENT '头像地址',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`dept_code` varchar(50) DEFAULT NULL COMMENT '部门编码',
`user_phone` varchar(15) DEFAULT NULL COMMENT '手机号',
`tenant` varchar(15) DEFAULT NULL COMMENT '租户',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统用户表';
CREATE DATABASE ds1;
USE ds1;
DROP TABLE IF EXISTS `sys_user0`;
CREATE TABLE `sys_user0` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`update_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
`create_user` varchar(100) NOT NULL COMMENT '创建人编码',
`update_user` varchar(100) NOT NULL COMMENT '修改人编码',
`delete_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记(1 删除 0未删除)',
`pos_code` varchar(50) DEFAULT NULL COMMENT '职位编码',
`disable_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '启用标记(1 禁用 0启用)',
`avatar` varchar(100) DEFAULT NULL COMMENT '头像地址',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`dept_code` varchar(50) DEFAULT NULL COMMENT '部门编码',
`user_phone` varchar(15) DEFAULT NULL COMMENT '手机号',
`tenant` varchar(15) DEFAULT NULL COMMENT '租户',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统用户表';
DROP TABLE IF EXISTS `sys_user1`;
CREATE TABLE `sys_user1` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`update_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
`create_user` varchar(100) NOT NULL COMMENT '创建人编码',
`update_user` varchar(100) NOT NULL COMMENT '修改人编码',
`delete_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标记(1 删除 0未删除)',
`pos_code` varchar(50) DEFAULT NULL COMMENT '职位编码',
`disable_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '启用标记(1 禁用 0启用)',
`avatar` varchar(100) DEFAULT NULL COMMENT '头像地址',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`user_name` varchar(50) DEFAULT NULL COMMENT '用户名',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`dept_code` varchar(50) DEFAULT NULL COMMENT '部门编码',
`user_phone` varchar(15) DEFAULT NULL COMMENT '手机号',
`tenant` varchar(15) DEFAULT NULL COMMENT '租户',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统用户表';
pom依赖
sql
<dependencies>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
</dependencies>
yml配置文件
bash
# 分库分表【数据分布在ds0.sys_user0、ds0.sys_user1、ds1.sys_user0、ds1.sys_user1】
spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/ds0?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
username: root
password: xx
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/ds1?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
username: root
password: xx
sharding:
# 默认数据源
default-data-source-name: ds0
# 分库分表规则
tables:
sys_user:
# 实际数据节点
actual-data-nodes: ds$->{0..1}.sys_user$->{0..1}
# 分库策略 - 使用最简单的hash算法
database-strategy:
inline:
sharding-column: id
algorithm-expression: ds$->{id.hashCode().abs() % 2}
# 分表策略 - 使用最简单的hash算法
table-strategy:
inline:
sharding-column: id
algorithm-expression: sys_user$->{(id.hashCode().abs() >> 1) % 2}
# 主键生成策略
key-generator:
column: id
type: SNOWFLAKE
props:
sql:
show: true
# 允许内联策略进行范围查询
allow:
range:
query:
with:
inline:
sharding: true