分库分表后会带来哪些问题?

#中间件#面试八股

分库分表后会带来哪些问题?

结论:分片把「一张表一个事务」拆成「N 个节点 N 份状态」,代价集中在七类:分布式事务(Distributed Transaction)、跨库 JOIN、跨库分页与聚合、全局唯一 ID、唯一约束、DDL 与运维、连接数膨胀。上线前逐条给出方案,否则分片当天就会变成线上事故。

一、问题全景

类别具体表现常用解法
分布式事务下单扣库存跨库后原子性丢失本地消息表、TCC、Saga、Seata AT
跨库 JOIN订单与用户不在同一库字段冗余、广播表、绑定表、应用层组装
分页排序limit 100000,20 要到各分片取 100020 行游标分页、二次查询、搜索引擎
聚合统计count/sum/group by 需归并中间件归并引擎、预聚合表、数仓
全局 ID自增主键跨库冲突雪花算法、号段模式、Redis
唯一约束跨分片唯一索引失效独立登记表、SETNX、基因法
DDL 变更上千张表要逐个 ALTER在线 DDL 工具、影子表灰度
连接数库数 × 连接池大小代理层连接复用、缩小池
运维备份、监控、慢查分散统一元数据、分片级指标

二、跨库 JOIN 与分页的放大效应

SELECT * FROM t_order ORDER BY id DESC LIMIT 100000, 20;
        │
        ├─ ds0.t_order_0 ── 取前 100020 行 ──┐
        ├─ ds0.t_order_1 ── 取前 100020 行 ──┤   内存归并排序
        ├─ ds1.t_order_0 ── 取前 100020 行 ──┤   丢弃前 100000 行
        └─ ds1.t_order_1 ── 取前 100020 行 ──┘   返回 20 行
                总扫描 400080 行,内存峰值随 offset 线性增长

偏移量越大,代价按「分片数 × offset」放大,这正是分片后深分页必须改造的原因。

三、分布式事务的最小实现

多数业务不需要强一致,用本地消息表加异步重试即可:

-- 每个分片库内都建一张本地消息表
CREATE TABLE t_local_msg (
  id          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  biz_no      VARCHAR(64)  NOT NULL COMMENT '业务单号,幂等键',
  topic       VARCHAR(64)  NOT NULL,
  payload     JSON         NOT NULL,
  status      TINYINT      NOT NULL DEFAULT 0 COMMENT '0待投递 1已投递 2已消费',
  retry_count INT          NOT NULL DEFAULT 0,
  next_time   DATETIME     NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uk_biz (biz_no, topic),
  KEY idx_status_time (status, next_time)
) ENGINE=InnoDB;
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
    orderMapper.insert(order);                 // 同一分片库
    localMsgMapper.insert(LocalMsg.of(order)); // 同一分片库,与业务同事务
}

// 独立投递线程扫描 status = 0,发送 MQ 成功后置 1
// 消费端以 biz_no 做幂等,失败则按 next_time 退避重试

四、连接数为什么暴涨

假设单实例 20 个连接、4 个分片库、8 个应用实例:4 × 20 × 8 = 640,还没算只读库与批处理任务。单机 MySQL 的 max_connections 通常在 1000 上下,节点一多就打满,表现为 Too many connections 与连接超时。

收敛手段:用 ShardingSphere-Proxy 之类的代理层复用后端连接、按分片调整池大小(热点库给大池)、把批处理任务拆到独立数据源。

五、常见追问

Q1:跨分片唯一性怎么保证? 在独立库建「唯一键登记表」,插入前 INSERT ... ON DUPLICATE KEY UPDATE 抢占;或用 Redis SETNX 加过期兜底;量小时直接让全局 ID 承担主键唯一性。

Q2:1000 张表的 DDL 怎么做? 先在影子表上执行验证,再用 gh-ost / pt-osc 逐表在线变更,由发布系统限速,避免同时打满磁盘 IO;变更期间禁止新增大字段。

Q3:聚合统计一定要实时吗? 不必。报表类需求走数仓或预聚合表,实时性要求高的计数放 Redis,只有对账类才需要跨分片精确归并。

小结

  • 分片的代价是系统性的:事务、JOIN、分页、ID、约束、DDL、连接数,逐条都要有预案。
  • 能用冗余字段和绑定表消掉的 JOIN,不要留给应用层归并。
  • 深分页与聚合是性能放大器,必须在设计期就定好替代方案。
笔记加载中…