分库分表的架构选型
单库单表在数据量达到千万级时面临查询性能下降、写入瓶颈、备份恢复时间长等问题。分库分表通过将数据水平拆分到多个数据库实例和表文件中,降低单节点数据量,提升整体吞吐能力。ShardingSphere-JDBC作为客户端分片方案,无需独立中间件部署,在应用层完成SQL路由和结果归并。
分片策略的核心是分片键(Sharding Key)的选择。分片键应满足三个条件:查询条件中高频出现的字段、取值分布均匀的字段、业务上不可变或极少变更的字段。订单系统通常选择user_id或order_id作为分片键,以用户维度或订单维度拆分数据。
Spring Boot集成ShardingSphere-JDBC
Maven依赖配置:
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc</artifactId>
<version>5.5.1</version>
</dependency>
application.yml配置分库分表规则:
spring:
datasource:
driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
url: jdbc:shardingsphere:classpath:sharding-config.yaml
sharding:
datasource:
names: ds0,ds1
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://10.0.0.1:3306/order_db0
username: root
password: password
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://10.0.0.2:3306/order_db1
username: root
password: password
tables:
t_order:
actual-data-nodes: ds${0..1}.t_order${0..3}
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: db-mod
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: table-mod
key-generate-strategy:
column: order_id
key-generator-name: snowflake
sharding-algorithms:
db-mod:
type: MOD
props:
sharding-count: 2
table-mod:
type: MOD
props:
sharding-count: 4
key-generators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 1
以上配置将t_order表拆分为2个数据库各4张表共8个分片。user_id作为分库键,取模2路由到ds0或ds1;order_id作为分表键,取模4路由到t_order0至t_order3。订单ID使用雪花算法生成,保证全局唯一和递增性。
分布式主键生成策略
ShardingSphere内置三种主键生成策略:SNOWFLAKE(雪花算法)、UUID和NANOID。雪花算法通过时间戳加机器ID加序列号组合生成64位整数,兼具全局唯一性和有序性,是分库分表场景的首选方案。
// 雪花算法结构(64位)
// | 1位符号 | 41位时间戳 | 10位机器ID | 12位序列号 |
// 理论上每台机器每毫秒可生成4096个ID
// 自定义主键生成器
public class CustomKeyGenerator implements KeyGenerateAlgorithm {
private final AtomicLong counter = new AtomicLong(0);
@Override
public Comparable<?> generateKey() {
long timestamp = System.currentTimeMillis();
long workerId = getWorkerId();
long sequence = counter.getAndIncrement() & 0xFFF;
return (timestamp << 22) | (workerId << 12) | sequence;
}
@Override
public String getType() {
return "CUSTOM";
}
}
部署多实例时需确保worker-id不同,否则会产生主键冲突。可通过Zookeeper或Nacos协调分配worker-id,或使用机器IP末段作为worker-id的简单方案。
跨分片查询与结果归并
分库分表后,非分片键查询需要广播到所有分片执行再归并结果。ShardingSphere自动处理广播查询和结果归并,但查询性能取决于分片数量和归并排序复杂度。
-- 按分片键查询 - 精准路由到单个分片
SELECT * FROM t_order WHERE user_id = 1001 AND order_id = 50001;
-- 路由到 ds1.t_order1
-- 按非分片键查询 - 广播到所有分片
SELECT * FROM t_order WHERE status = 'PAID' ORDER BY create_time DESC LIMIT 20;
-- 路由到所有8个分片,各分片取20条,归并后取Top20
-- 分页查询的深度分片问题
SELECT * FROM t_order ORDER BY create_time DESC LIMIT 10000, 20;
-- 需要各分片取10020条归并,深度分页时性能急剧下降
深度分页问题的优化方案:使用游标分页替代OFFSET分页,记录上一页最后一条记录的排序键值作为查询条件:
-- 传统分页(深度分页性能差)
SELECT * FROM t_order ORDER BY create_time DESC LIMIT 10000, 20;
-- 游标分页(性能稳定)
SELECT * FROM t_order
WHERE create_time < '2026-08-24 10:30:00'
ORDER BY create_time DESC
LIMIT 20;
广播表与绑定表配置
字典表、配置表等数据量小但需频繁关联查询的表配置为广播表,在所有数据节点存储完整副本,JOIN查询时无需跨库:
sharding:
broadcast-tables:
- t_dict
- t_config
binding-tables:
- t_order,t_order_item
绑定表(Binding Table)指具有相同分片策略的主子表,如t_order和t_order_item按order_id分片。配置为绑定表后,两者的JOIN查询在同一分片内完成,避免笛卡尔积式的跨库关联。
分布式事务处理
跨库操作需要分布式事务保证数据一致性。ShardingSphere支持三种事务类型:LOCAL(本地事务)、XA(两阶段提交)和BASE(柔性事务)。
// XA事务配置
spring:
shardingsphere:
props:
xa-transaction-manager-type: Atomikos
// 代码中使用
@Transactional
public void createOrder(OrderDTO dto) {
// 订单写入ds0
orderMapper.insert(dto);
// 订单详情写入ds1(与订单分片键一致则同库)
orderItemMapper.insertBatch(dto.getItems());
// 库存扣减可能在不同库
stockMapper.deduct(dto.getSkuId(), dto.getQuantity());
}
XA事务保证强一致性但性能开销大,适合金融场景。BASE事务通过Seata AT模式实现最终一致性,性能更高,适合电商等容忍短暂不一致的场景。选择事务策略时需在一致性和性能之间权衡。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/sql-fen-ku-fen-biao-shi-zhan-shardingsphere-fen-pian-ce/