1. 项目背景与核心挑战
去年双十一大促期间,我们电商平台的订单库单表数据量突破了8000万条,查询响应时间从平均200ms飙升到2秒以上。DBA团队紧急扩容了服务器配置,但性能提升不到15%。这让我意识到:传统的单库单表架构已经触达天花板,分库分表势在必行。
但真正实施时才发现,分库分表远不止是技术方案选型那么简单。从架构设计、数据迁移到最终上线,整个过程就像给飞行中的飞机更换引擎——既要保证业务连续性,又要实现架构升级。以下是我们在三次迭代中积累的实战经验,包含踩过的坑和验证过的解决方案。
2. 架构设计阶段
2.1 分片策略选择
我们对比了三种主流分片方案:
- 哈希分片:对user_id取模,数据分布均匀但扩容困难
- 范围分片:按订单创建时间分片,存在热点问题
- 目录分片:通过路由表维护映射关系,灵活性最高
最终选择复合分片策略:先用时间范围分库(每月一个库),再用user_id哈希分表。这样既避免单库数据无限增长,又保证单个用户查询只需访问特定分片。关键配置示例:
sql复制-- 分库规则(按年月)
sharding_db_202301
sharding_db_202302
-- 分表规则(16个表)
order_table_0 到 order_table_15
2.2 中间件选型
测试了三种分库分表中间件的表现:
| 中间件 | 连接池管理 | SQL兼容性 | 分布式事务 | 运维复杂度 |
|---|---|---|---|---|
| ShardingSphere | 优秀 | 支持90%语法 | 支持XA | 中等 |
| MyCat | 良好 | 部分DDL受限 | 弱支持 | 较高 |
| 自研方案 | 需自行实现 | 完全可控 | 灵活定制 | 极高 |
选择ShardingSphere-Proxv4.1.1,因其:
- 支持MySQL协议透传,应用无需改造SQL
- 提供柔性事务解决方案
- 有完善的监控指标暴露
3. 数据迁移实施
3.1 双写方案设计
采用增量同步+全量校验的混合模式:
- 先开启双写(新老库同时写入)
- 用DataX全量迁移历史数据
- 通过时间戳比对增量数据
- 最终用CRC32校验数据一致性
关键配置示例:
java复制// 双写切面示例
@Around("execution(* com..mapper.OrderMapper.insert*(..))")
public Object dualWrite(ProceedingJoinPoint pjp) {
Object result = pjp.proceed(); // 写主库
shardingDbMapper.insert(pjp.getArgs()); // 写分片库
return result;
}
3.2 灰度发布策略
制定五阶段发布计划:
| 阶段 | 流量比例 | 验证重点 | 回滚方案 |
|---|---|---|---|
| 影子库 | 0% | SQL兼容性测试 | 直接关闭影子路由 |
| 读灰度 | 5% | 查询结果一致性 | 修改负载均衡配置 |
| 写灰度 | 10% | 双写性能损耗 | 停用双写切面 |
| 全量读 | 100% | 高并发查询稳定性 | 切换回老库连接 |
| 全量写 | 100% | 事务一致性监控 | 启用老库应急写入通道 |
4. 上线后监控优化
4.1 关键监控指标
在Grafana配置的监控看板包含:
- 分片均衡度:各表数据量差异不超过15%
- 跨库查询比例:控制在总查询量的5%以内
- 分布式事务成功率:要求99.99%以上
- 慢查询TOP10:重点关注JOIN操作
4.2 典型问题处理
案例1:分页查询性能劣化
- 现象:
LIMIT 10000,10查询超时 - 原因:分片后需全表扫描再归并排序
- 解决方案:
- 改用ES做分页查询
- 添加
sharding_key条件缩小扫描范围
案例2:分布式事务阻塞
- 现象:秒杀活动出现库存超卖
- 原因:XA事务超时设置过短(默认30s)
- 优化:调整参数并引入本地消息表
properties复制# ShardingSphere配置
spring.shardingsphere.props.xa-transaction-timeout-seconds=120
5. 经验总结
- 拆分粒度选择:建议单表控制在500-1000万数据量级,我们实践发现800万是个临界点
- 字段设计规范:
- 必须包含分片键字段
- 避免使用自增主键(改用雪花ID)
- 所有时间字段统一为UTC时间戳
- 连接池配置:
yaml复制# 建议配置 spring: shardingsphere: datasource: maxPoolSize: 50 # 每个物理库连接数 minPoolSize: 10 connectionTimeoutMilliseconds: 3000
这套方案最终让我们在零停机的情况下,将数据库吞吐量提升了8倍,TP99从1200ms降到150ms。最关键的是建立了可扩展的数据架构,后续扩容只需调整分片规则即可。