# 常见问题及解决方案
作者:Ethan.Yang
博客:https://blog.ethanyang.cn (opens new window)
代码参考:[https://github.com/YangYingmeng/learning_shardingJDBC)
# 一、SQL排序、翻⻚、函数计算
在未分库分表之前,所有数据天然集中在 单库单表 中:
ORDER BYLIMITCOUNT / SUM / AVG- 各类聚合函数
都可以直接由 单个数据库实例一次性完成,不存在跨节点问题。
分库分表后带来的本质问题
分库分表后,数据被打散到多个物理节点,例如:
ds0.order_0
ds0.order_1
ds1.order_0
ds1.order_1
2
3
4
当执行如下 SQL:
SELECT * FROM orders ORDER BY create_time DESC LIMIT 10;
实际上会变成:
- 在 每个分片节点上分别执行 SQL
- 每个节点各自返回一批结果
- ShardingSphere 在 结果归并层进行二次排序
- 再在内存中统一做
LIMIT截断
这就引出了几个核心问题:
- 排序字段是 分片键 → 只命中一个分片,开销最小
- 排序字段是 非分片键 → 所有分片全量扫描 + 二次排序,CPU / IO 开销显著上升
- 大页分页(如
LIMIT 100000, 20)→ 每个分片都要扫描大量无效数据
设计层面的核心原则
分页、排序、统计类 SQL 的设计原则只有一句话:
尽可能让查询只落在“一个分片”上,而不是“全分片归并”。
具体落地:
ORDER BY字段 ≈ 分片键WHERE条件中必须包含分片键- 避免:
ORDER BY 非分片字段 + 全表分页 - 避免:
COUNT(*)在全分片上做实时统计
# 二、数据库全局主键重复
| 方案 | 是否唯一 | 是否有序 | 性能 | 说明 |
|---|---|---|---|---|
| UUID | ✅ | ❌ | ❌ | 长、无序、索引膨胀 |
| Redis 号段 | ✅ | ✅ | ✅ | 需要维护号段服务 |
| 雪花算法 | ✅ | ✅ | ✅ | 分布式最常用 |
# 三、跨节点 Join 关联与多维度查询
在单库时代:
SELECT *
FROM order o
JOIN user u ON o.user_id = u.id;
2
3
数据库内部可以通过 Join 一次完成。
分库分表后的根本变化
当:
order表按user_id分库user表按id分库- 二者 分片维度不一致
那么:
SQL Join 将不可避免地产生 跨节点 Join
ShardingSphere 只能拆解为:
- 多库查
order - 多库查
user - 在 JVM 内存中做 结果 Join
该模式:
- CPU 消耗大
- 网络开销大
- 排序 + 分页极其复杂
- 不适合高并发核心链路
三种主流解决方案
方案一:字段冗余(最推荐)
经典场景:
订单需要展示用户昵称、头像,但用户表在另一库
解决方案:
- 在
order表中冗余:user_name、user_avatar - 下单时一次性写入
- 后续查询 不再 Join user 表
这是 互联网系统中使用最广泛的方案。
方案二:广播表(配置型小表)
适合:
- 基础字典表
- 配置表
- 地区表
- 商品类目树
特点:
- 每个库都有一份完整数据
- Join 时不跨库
- 以空间换时间
方案三:搜索系统 / NoSQL 汇总
典型场景:
用户维度查订单很方便(
user_id分片) 商家维度查订单非常困难(shop_id非分片键)
解决方式:
- 将订单数据同步一份到:
- ES / ClickHouse / HBase
- 专门支持:
- 商家后台
- 报表系统
- 搜索系统
# 四、分布式事事务
分库分表后,凡是涉及:
- 多个数据库
- 多个服务
- 多个资源
就天然进入 分布式事务场景。
服务自管理 + 最终一致性
核心思想不是“全局强一致”,而是:
✅ 每个服务只保证自己的本地事务强一致 ✅ 跨服务靠 消息 + 幂等 + 重试 + 状态机 + 补偿 达成最终一致
本地事务 + MQ 事务消息
↓
消费者幂等 + 乐观锁
↓
失败重试 + DLQ
↓
失败记录 + 人工/自动补偿
2
3
4
5
6
7
# 五、容量规划-分库分表后⼆次扩容
分库分表不是“一劳永逸”,而是需要面对:
- 数据持续增长
- 单库数据再次逼近瓶颈
- 热点用户 / 热点商户问题
二次扩容的核心难点
- 早期规则:
user_id % 2 - 后期想扩成:
user_id % 4 - 旧数据和新数据 路由规则完全不一致
- 涉及:
- 全量迁移
- 双写
- 数据校验
- 切流量
可行应对策略
| 方案 | 说明 |
|---|---|
| 逻辑分片 + 虚拟节点 | 提前预留分片位,避免强制迁移 |
| 新老数据双写 | 新数据走新分片 |
| 离线迁移 | 对历史数据做批量平滑迁移 |
| 中间层路由改造 | 通过路由表进行新旧分片映射 |
设计阶段的重要原则
✅ 分片规则一旦上线,尽量“终身不变” ✅ 初始就按未来 2~3 年的容量设计 ✅ 不要“先 %2,后面再说”