分库分表的触发时机与架构决策
数据库分库分表不是性能优化的首选方案。当单表数据量超过5000万行、单库QPS超过3万、或数据增长速度导致备份窗口无法覆盖时,才考虑分库分表。过早分片引入的分布式事务、跨片查询、数据迁移等问题远比单库慢查询更难解决。
分库分表有两种架构路径:客户端分片(ShardingSphere-JDBC)和代理分片(ShardingSphere-Proxy)。客户端分片性能更好(无网络跳转),但与应用强耦合;代理分片对应用透明,但增加一跳延迟。读写分离场景推荐代理模式,纯分片场景推荐客户端模式。
ShardingSphere-JDBC路由策略配置
ShardingSphere支持精确分片、范围分片、复合分片和Hint分片四种路由策略。选择路由策略的依据是查询模式。
# application.yml - ShardingSphere分片配置
spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://mysql-0:3306/order_db_0
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://mysql-1:3306/order_db_1
rules:
sharding:
tables:
t_order:
actual-data-nodes: ds${0..1}.t_order_${0..15}
table-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: order-table-inline
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: order-db-mod
key-generate-strategy:
column: order_id
key-generator-name: snowflake
sharding-algorithms:
order-table-inline:
type: INLINE
props:
algorithm-expression: t_order_${user_id % 16}
order-db-mod:
type: MOD
props:
sharding-count: 2
此配置中,user_id决定数据路由:先按user_id % 2选库,再按user_id % 16选表。这种两级分片策略确保同一用户的所有订单落在同一库同一表,避免跨库查询。
分片键选择的核心原则
分片键决定了数据的分布和查询的效率。选错分片键等于将分库分表的性能瓶颈从数据库转移到应用层。
高基数原则:分片键的值域必须足够大,保证数据均匀分布。user_id、order_id是好的分片键,status、type等低基数字段会导致数据倾斜。
查询亲和原则:分片键必须出现在最频繁的查询条件中。订单系统最频繁的查询是”按用户查订单”,因此user_id是合理的分片键。
避免跨片JOIN原则:有关联关系的表尽量使用相同分片键。订单表和订单明细表都以user_id为分片键,可以保证关联查询落在同一分片上。
当查询条件不含分片键时,ShardingSphere会执行全路由扫描(所有分片都查),性能退化到分片前的水平甚至更差。这种场景需要通过广播表、绑定表或冗余字段来规避。
跨片查询优化策略
跨片查询是分库分表后最大的性能挑战。以下三种策略按优先级排列:
1. 绑定表(Binding Tables)
将分片规则相同的表声明为绑定表,ShardingSphere在解析SQL时将笛卡尔积查询优化为内连接查询,避免无效的跨片组合。
# 绑定表配置
rules:
sharding:
binding-tables:
- t_order,t_order_item
tables:
t_order_item:
actual-data-nodes: ds${0..1}.t_order_item_${0..15}
table-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: order-table-inline
2. 广播表(Broadcast Tables)
小数据量的维度表(如地区表、品类表)配置为广播表,每个库都存一份全量数据,避免跨库JOIN。
# 广播表配置
rules:
sharding:
broadcast-tables:
- t_region
- t_category
3. 冗余字段与宽表
在订单表中冗余存储merchant_name字段,避免查询订单时需要关联商户表。冗余带来的数据一致性风险通过消息队列异步同步解决。
-- 冗余字段同步方案
UPDATE t_order SET merchant_name = ? WHERE merchant_id = ?;
-- ShardingSphere自动路由到所有相关分片
分页查询的跨片归并优化
分页查询在分片环境下性能极差。SELECT * FROM t_order ORDER BY create_time LIMIT 100000, 10在16个分片上实际查询的是16 x 100010条记录再归并,内存和时间消耗远超预期。
优化方案:禁止深度分页,改为游标分页。
// 游标分页实现
public PageResult<Order> queryByCursor(Long lastOrderId, int pageSize) {
String sql = "SELECT * FROM t_order " +
"WHERE order_id > ? ORDER BY order_id LIMIT ?";
List<Order> orders = jdbcTemplate.query(sql,
(rs, rowNum) -> mapOrder(rs),
lastOrderId, pageSize);
return new PageResult<>(
orders,
orders.isEmpty() ? null : orders.get(orders.size() - 1).getOrderId()
);
}
游标分页要求排序字段与分片键一致或有索引。当排序字段不是分片键时,仍需全分片扫描。这种场景推荐引入ES作为查询索引,MySQL仅作为数据存储,复杂查询走ES,精确查询走MySQL分片。
数据迁移与扩容方案
分片扩容(如从2库扩到4库)需要数据迁移。ShardingSphere的弹性伸缩模块支持在线扩容,但该功能仍在社区孵化阶段。生产环境更成熟的方案是双写+数据校验:
1. 应用层同时写旧分片和新分片(双写),新分片按新规则路由。
2. 后台任务按分片键范围将旧数据迁移到新分片。
3. 数据校验脚本对比新旧分片数据一致性。
4. 确认一致后切换读流量到新分片,再切换写流量。
5. 下线旧分片。整个过程中业务零中断。
分库分表是数据库层面的架构决策,实施前必须评估3年内的数据增长预期。过细的分片增加运维成本,过粗的分片很快需要扩容。起始分片数建议为预估峰值的2-4倍,留出足够的扩容缓冲期。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/shu-ju-ku-fen-ku-fen-biao-shi-zhan-shardingsphere-lu-you-ce/