1. 分库分表后的分布式访问挑战
当单表数据量突破千万级时,MySQL的性能瓶颈会逐渐显现。去年我们电商平台的订单表就遇到了这个问题——查询响应时间从200ms飙升到2秒以上。这时候分库分表就成了必然选择,但随之而来的分布式访问问题却让团队踩了不少坑。
最典型的场景就是跨库查询。比如用户要查自己半年内的订单,如果订单数据分散在4个分库中,简单的SELECT语句就变成了噩梦。我们曾经遇到过这样的案例:一个需要聚合3个分库数据的统计查询,竟然把数据库CPU跑满了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 分布式访问的核心解决方案
2.1 分布式事务处理
在订单支付场景中,需要同时更新订单库和账户库。我们最终选择了Seata的AT模式,它的工作原理是:
- 事务发起方生成全局XID
- 各分支事务注册到TC(事务协调器)
- 执行阶段记录before image
- 提交时异步删除undo log
关键配置示例:
java复制@GlobalTransactional
public void purchase(String userId, String commodityCode, int count) {
// 扣减库存
storageFeignClient.deduct(commodityCode, count);
// 创建订单
orderFeignClient.create(userId, commodityCode, count);
}
注意:AT模式不适合高频短事务,会产生额外30%左右的性能开销。
2.2 分布式ID生成方案
我们对比了几种方案后选择了Leaf-segment:
| 方案 | 吞吐量 | 趋势递增 | 缺点 |
|---|---|---|---|
| UUID | 极高 | 否 | 无序导致索引效率低 |
| 雪花算法 | 高 | 是 | 时钟回拨问题 |
| 数据库自增ID | 低 | 是 | 单点瓶颈 |
| Leaf | 中高 | 是 | 需要维护号段 |
Leaf的配置关键点:
properties复制leaf.name=order
leaf.segment.enable=true
leaf.jdbc.url=jdbc:mysql://127.0.0.1:3306/leafdb
2.3 跨库JOIN的解决之道
我们采用了两层方案:
- 应用层JOIN:先查询主表获取ID,再批量查询关联表
- 数据冗余:将常用查询字段冗余到主表
例如用户查询订单列表:
sql复制-- 先查订单ID
SELECT order_id FROM order_01 WHERE user_id=123;
-- 批量查订单详情
SELECT * FROM order_detail WHERE order_id IN (1001,1002,1003);
3. 中间件选型与实践
3.1 ShardingSphere实战
我们的分库分表示例配置:
yaml复制spring:
shardingsphere:
datasource:
names: ds0,ds1
sharding:
tables:
t_order:
actual-data-nodes: ds$->{0..1}.t_order_$->{0..15}
database-strategy:
inline:
sharding-column: user_id
algorithm-expression: ds$->{user_id % 2}
table-strategy:
inline:
sharding-column: order_id
algorithm-expression: t_order_$->{order_id % 16}
遇到的坑:
- 批量插入需要rewriteBatchedStatements=true
- 分布式序列需要配置spring.shardingsphere.sharding.tables.t_order.key-generator.column=order_id
3.2 MyCat的读写分离配置
对于报表类查询,我们使用MyCat做读写分离:
xml复制<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="master:3306" user="root" password="123456">
<readHost host="hostS1" url="slave1:3306" user="root" password="123456"/>
</writeHost>
</dataHost>
4. 性能优化实战记录
4.1 慢查询治理案例
我们曾遇到一个跨库分页查询需要8秒,优化过程:
- 使用ES实现异构存储,同步订单数据
- 查询改用ES的search_after实现深度分页
- 建立user_id+create_time的联合索引
优化后查询耗时降至200ms内。
4.2 缓存策略设计
多级缓存方案:
- 本地缓存(Caffeine):缓存用户维度的热点数据
java复制LoadingCache<String, List<Order>> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build(key -> queryOrdersFromDB(key));
- Redis集群:存储全量订单数据,设置不同的TTL
- 缓存击穿防护:使用Redisson的分布式锁
5. 监控与运维体系建设
5.1 全链路监控方案
我们搭建的监控体系包括:
- Prometheus采集ShardingSphere的metrics
- SkyWalking追踪分布式事务链路
- 自定义分片键分布监控
关键告警规则示例:
yaml复制- alert: ShardingSphere_Error_SQL
expr: rate(shardingsphere_statement_errors_total[1m]) > 5
for: 5m
labels:
severity: critical
5.2 数据迁移实践
在线扩容时的数据迁移步骤:
- 使用ShardingSphere-Scaling启动迁移任务
- 配置双写策略
- 校验数据一致性
- 切换流量
迁移过程中需要特别注意:
- 控制迁移速度防止源库压力过大
- 业务低峰期执行校验
- 准备回滚方案
这套分布式访问解决方案在我们日均百万订单的系统上稳定运行了两年多。最大的体会是:没有银弹方案,需要根据业务特点组合不同的技术手段。比如对一致性要求高的支付业务采用强一致方案,而对查询性能要求高的场景适当使用最终一致性。
