# 常见问题及解决方案

作者:Ethan.Yang
博客:https://blog.ethanyang.cn (opens new window)
代码参考:[https://github.com/YangYingmeng/learning_shardingJDBC)


# 一、SQL排序、翻⻚、函数计算

在未分库分表之前,所有数据天然集中在 单库单表 中:

  • ORDER BY
  • LIMIT
  • COUNT / SUM / AVG
  • 各类聚合函数

都可以直接由 单个数据库实例一次性完成,不存在跨节点问题。

分库分表后带来的本质问题

分库分表后,数据被打散到多个物理节点,例如:

ds0.order_0
ds0.order_1
ds1.order_0
ds1.order_1
1
2
3
4

当执行如下 SQL:

SELECT * FROM orders ORDER BY create_time DESC LIMIT 10;
1

实际上会变成:

  1. 每个分片节点上分别执行 SQL
  2. 每个节点各自返回一批结果
  3. ShardingSphere 在 结果归并层进行二次排序
  4. 再在内存中统一做 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;
1
2
3

数据库内部可以通过 Join 一次完成。

分库分表后的根本变化

当:

  • order 表按 user_id 分库
  • user 表按 id 分库
  • 二者 分片维度不一致

那么:

SQL Join 将不可避免地产生 跨节点 Join

ShardingSphere 只能拆解为:

  • 多库查 order
  • 多库查 user
  • 在 JVM 内存中做 结果 Join

该模式:

  • CPU 消耗大
  • 网络开销大
  • 排序 + 分页极其复杂
  • 不适合高并发核心链路

三种主流解决方案

方案一:字段冗余(最推荐)

经典场景:

订单需要展示用户昵称、头像,但用户表在另一库

解决方案:

  • order 表中冗余:user_nameuser_avatar
  • 下单时一次性写入
  • 后续查询 不再 Join user 表

这是 互联网系统中使用最广泛的方案

方案二:广播表(配置型小表)

适合:

  • 基础字典表
  • 配置表
  • 地区表
  • 商品类目树

特点:

  • 每个库都有一份完整数据
  • Join 时不跨库
  • 以空间换时间

方案三:搜索系统 / NoSQL 汇总

典型场景:

用户维度查订单很方便(user_id 分片) 商家维度查订单非常困难(shop_id 非分片键)

解决方式:

  • 将订单数据同步一份到:
    • ES / ClickHouse / HBase
  • 专门支持:
    • 商家后台
    • 报表系统
    • 搜索系统

# 四、分布式事事务

分库分表后,凡是涉及:

  • 多个数据库
  • 多个服务
  • 多个资源

就天然进入 分布式事务场景

服务自管理 + 最终一致性

核心思想不是“全局强一致”,而是:

每个服务只保证自己的本地事务强一致 ✅ 跨服务靠 消息 + 幂等 + 重试 + 状态机 + 补偿 达成最终一致

本地事务 + MQ 事务消息
        ↓
消费者幂等 + 乐观锁
        ↓
失败重试 + DLQ
        ↓
失败记录 + 人工/自动补偿
1
2
3
4
5
6
7

# 五、容量规划-分库分表后⼆次扩容

分库分表不是“一劳永逸”,而是需要面对:

  • 数据持续增长
  • 单库数据再次逼近瓶颈
  • 热点用户 / 热点商户问题

二次扩容的核心难点

  • 早期规则:user_id % 2
  • 后期想扩成:user_id % 4
  • 旧数据和新数据 路由规则完全不一致
  • 涉及:
    • 全量迁移
    • 双写
    • 数据校验
    • 切流量

可行应对策略

方案 说明
逻辑分片 + 虚拟节点 提前预留分片位,避免强制迁移
新老数据双写 新数据走新分片
离线迁移 对历史数据做批量平滑迁移
中间层路由改造 通过路由表进行新旧分片映射

设计阶段的重要原则

分片规则一旦上线,尽量“终身不变” ✅ 初始就按未来 2~3 年的容量设计 ✅ 不要“先 %2,后面再说”