1. 分库分表不是银弹:为什么我们开始反思ShardingJDBC
去年双十一大促前,我们团队花了三个月把核心交易库从单实例改造成了ShardingJDBC分片集群。上线当晚,DBA突然发现订单表的自增ID出现了大面积重复。紧急回滚后排查发现,是分片键和分布式序列的配置冲突导致。这个事故让我深刻意识到:分库分表不是简单的技术选型问题,而是需要综合考量的架构决策。
在数据库性能优化的讨论中,"分库分表"几乎成了条件反射式的标准答案。但真实场景中,像ShardingJDBC这样的分片工具存在不少隐性成本:
- 配置复杂度指数级上升:一个简单的多表关联查询,在分片环境下需要考虑路由策略、绑定表配置、分布式事务等至少6个维度的参数
- 运维监控体系重构:原先针对单实例的监控指标(如QPS、连接数)在分片后需要重新定义聚合逻辑
- 业务侵入性强:分片键的选择往往需要改造业务逻辑,比如把用户ID从业务属性提升为架构约束
重要提示:在考虑分库分表前,建议先用EXPLAIN分析慢查询,90%的性能问题其实通过索引优化就能解决
2. ShardingJDBC的典型问题场景剖析
2.1 分布式事务的雪崩风险
某社交平台使用ShardingJDBC拆分用户表后,发现私信功能的已读状态经常不一致。根本原因是跨分片更新时,柔性事务(BASE)的最终一致性保障与业务要求的强一致性冲突。具体表现为:
- 用户A在分片1发送消息
- 用户B在分片2读取消息并标记已读
- 由于网络延迟,分片1的已读状态同步滞后
- 用户A刷新页面看到未读提示
这种场景下,要么接受短暂的数据不一致,要么改用XA事务——但后者会使平均延迟从20ms飙升到300ms以上。
2.2 分页查询的性能陷阱
电商系统按店铺ID分片后,运营后台的"全量订单查询"功能变得极其缓慢。测试数据显示:
| 数据量 | 单库查询耗时 | 分片查询耗时 |
|---|---|---|
| 10万 | 120ms | 1.8s |
| 50万 | 600ms | 9.4s |
| 100万 | 1.2s | 22s |
这是因为分页语句LIMIT 10000,20会被改写成各分片的LIMIT 0,10020,导致大量无效数据传输。更可怕的是,这种性能劣化在测试环境的小数据量下很难被发现。
3. 比ShardingJDBC更优先的优化方案
3.1 索引优化的实战案例
某知识社区的文章表有3000万数据,列表页加载需要4秒。团队第一反应是分表,但实际优化过程是:
- 用
pt-index-usage工具分析发现:90%的查询只使用主键ID索引 - 添加组合索引
(category_id, create_time)后,查询降到800ms - 进一步使用覆盖索引
INCLUDE (title,cover_url),最终稳定在200ms内
这个案例的成本仅是2小时DBA工作时间,而分库分表至少需要2周开发+3天停机迁移。
3.2 读写分离的配置技巧
对于读多写少的场景,用MySQL原生复制方案往往比ShardingJDBC更简单可靠。关键配置点:
# 建议的读写分离配置 spring: datasource: dynamic: primary: master strict: true datasource: master: url: jdbc:mysql://master:3306/db slave1: url: jdbc:mysql://slave1:3306/db read-only: true slave2: url: jdbc:mysql://slave2:3306/db read-only: true配合@DS("slave")注解,可以实现零侵入的读流量分发。注意要设置spring.datasource.dynamic.strict=true,避免写操作误路由到从库。
4. 必须分片时的最佳实践
当单表确实超过500万行且索引优化收效甚微时,可以考虑分片方案。以下是血泪教训总结的checklist:
分片键选择三原则:
- 避免热点:不要用性别、状态等低区分度字段
- 减少跨片:优先选最常用查询条件的字段
- 保持稳定:不要用可能修改的字段如手机号
必须配置的监控项:
/* 各分片数据量均衡检查 */ SELECT table_schema, table_name, ROUND(SUM(data_length)/1024/1024) AS size_mb, COUNT(*) AS rows_count FROM information_schema.tables WHERE table_schema NOT IN ('mysql','information_schema') GROUP BY table_schema, table_name; /* 跨分片查询比例监控 */ SHOW STATUS LIKE 'ShardingSphere-%';灰度上线策略:
- 第一阶段:双写单库,对比数据一致性
- 第二阶段:读流量切10%,观察慢查询
- 第三阶段:全量切换,保留旧库1周回滚窗口
最近我们帮一个客户优化分片配置时发现,他们80%的跨库查询其实可以通过broadcast-table配置避免。比如省份编码表只有几十条记录,完全可以在每个分片都保留全量副本。
5. 架构选型的决策框架
根据三十多个项目的复盘数据,我总结出数据库优化的决策树:
QPS<2000且数据量<500万:
- 优化索引 + 适当缓存
QPS在2000-5000或数据量500-1000万:
- 读写分离 + 查询重构
- 考虑TiDB等NewSQL方案
QPS>5000且数据量>1000万:
- 评估分库分表成本
- 优先垂直分库(按业务拆分)
- 最后考虑水平分片
有个反直觉的发现:使用ShardingJDBC的项目中,有47%在两年内迁移到了其他方案(如TiDB、OceanBase)。主要痛点集中在分布式事务和复杂查询的支持上。
真正需要ShardingJDBC的场景,通常是已有成熟MySQL生态且需要渐进式改造的存量系统。对于新项目,或许应该先问问:我们真的需要关系型数据库吗?