什么场景下要做分库分表
单表数据量超过5000万行、单库TPS压到3000以上、慢SQL频率明显上升——这三条满足任何一条,就要考虑拆分了。分库分表不是上来就做的架构决策,而是在读写压力、数据量、运维成本之间找不到平衡点时的最后手段。ShardingSphere作为目前最成熟的分库分表中间件,支持Proxy和JDBC两种模式,小团队用JDBC模式嵌入应用就够了。
拆分策略选择:垂直拆分vs水平拆分
垂直拆分按业务边界拆——用户表放用户库,订单表放订单库,逻辑清晰,适合微服务拆分场景。水平拆分按数据规则拆——同一个表的数据按user_id或order_id分到不同库和表,解决单表数据量问题。
实际业务往往是两者结合:先按业务垂直拆库,再对大表水平分表。以电商场景为例:
垂直拆分:
user_db: 用户表、角色表
order_db: 订单表、订单明细表
product_db: 商品表、库存表
水平拆分(order_db中的订单表):
order_db_0: t_order_0, t_order_1
order_db_1: t_order_0, t_order_1
ShardingSphere-JDBC配置实战
Spring Boot项目接入ShardingSphere-JDBC 5.x:
<!-- pom.xml -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>5.5.0</version>
</dependency>
application.yml分片配置:
mode:
type: Standalone
repository:
type: JDBC
dataSources:
ds_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://127.0.0.1:3306/order_db_0
username: root
password: root
ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://127.0.0.1:3306/order_db_1
username: root
password: root
rules:
- !SHARDING
tables:
t_order:
actualDataNodes: ds_${0..1}.t_order_${0..1}
tableStrategy:
standard:
shardingColumn: order_id
shardingAlgorithmName: t_order_mod
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: ds_mod
keyGenerateStrategy:
column: order_id
keyGeneratorName: snowflake
shardingAlgorithms:
t_order_mod:
type: MOD
props:
sharding-count: 2
ds_mod:
type: MOD
props:
sharding-count: 2
keyGenerators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 1
这段配置的含义:t_order表按order_id取模分2张表(t_order_0, t_order_1),按user_id取模分2个库(ds_0, ds_1)。主键用雪花算法生成,避免分库后的ID冲突。
分片键选择的关键原则
分片键直接决定数据分布均匀度和查询效率,选错代价极大。几个硬性原则:
1. 查询必带:分片键必须是几乎所有查询都带的字段。订单表按user_id分,那么查询订单必须带user_id,否则会全库全表扫描。
2. 数据均匀:避免热点。按create_time分片,写操作全部集中在当天的分片上;按user_id取模分布更均匀。
3. 不可变更:分片键一旦确定,后续修改分片键意味着数据重分布,代价等于做一次迁移。
如果业务上有些查询不带分片键怎么办?ShardingSphere支持绑定表和广播表:
# 绑定表:关联查询时避免笛卡尔积
bindingTables:
- t_order, t_order_item
# 广播表:每个库都存一份全量数据(如字典表)
broadcastTables:
- t_dict
分页查询处理
分库分表后分页是最棘手的问题。SELECT * FROM t_order ORDER BY create_time LIMIT 100,10 在分片环境下,ShardingSphere需要从所有分片取100+10条数据再合并排序,页码越深性能越差。
优化方案——禁止深分页,改用游标分页:
-- 禁止的方式
SELECT * FROM t_order WHERE user_id = 1001
ORDER BY create_time DESC LIMIT 10000, 10
-- 游标分页:基于上一页最后一条的时间戳
SELECT * FROM t_order
WHERE user_id = 1001 AND create_time < '2026-07-20 10:30:00'
ORDER BY create_time DESC LIMIT 10
游标分页每个分片只需取10条,合并后总数据量 = 分片数 × 10,性能稳定不受页码影响。
数据迁移策略
已有大表做分片,不能停服迁移。用双写+增量同步方案:
1. 新代码同时写旧表和新的分片表(双写)
2. 用脚本把旧表存量数据迁移到分片表
3. 校验新旧数据一致性
4. 读流量切到分片表
5. 停止写旧表
ShardingSphere本身不提供迁移工具,但可以配合Canal监听binlog做增量同步。核心是保证双写期间数据不丢,校验通过后再切流量。
运维监控与问题排查
ShardingSphere自带SQL日志,开发阶段打开能看到路由到哪个分片:
props:
sql-show: true
日志输出:Logic SQL: SELECT * FROM t_order WHERE user_id=1001 AND order_id=2001 → Actual SQL: ds_0 ::: SELECT * FROM t_order_1 WHERE user_id=1001 AND order_id=2001
生产关闭sql-show避免性能损耗,改用Prometheus采集ShardingSphere的指标:parse_query_count(SQL解析次数)、route_result_count(路由结果数)。如果route_result_count接近分片总数,说明出现了全路由查询,需要检查分片键覆盖情况。
常见坑:跨分片聚合函数结果不准、不含分片键的UPDATE语句全路由执行导致死锁、DDL变更需要同步到所有分片。这些在架构评审阶段就要纳入规范,写进研发手册。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-fen-ku-fen-biao-shi-zhan-shardingsphere-chui-zhi-yu/