MySQLNotes

第 28 章:分库分表与数据迁移

zjc 于 2026-01-28 发布

这是《MySQL 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 当单实例容量、写入吞吐、连接数或维护窗口无法满足业务时,需要拆分数据。分库分表是最后的水位手段,必须先穷尽索引、SQL、架构和读写分离优化。

28.1 什么时候需要分

判断信号:

维度 信号
容量 单表或单实例磁盘持续增长
写入 主库 CPU、IO、Redo、Binlog 达到上限
DDL 索引维护和大表变更窗口不足
备份 全量备份和恢复时间超过目标
复制 大事务造成长期延迟
热点 单表或单行写入不可拆
运维 单表行数影响统计和归档

不建议只按“单表超过 2000 万行”这类绝对值决策。行宽、索引数、访问模式、Buffer Pool 和磁盘能力都影响实际边界。

28.2 先做垂直拆分

垂直拆分按业务或字段拆:

订单库:orders、order_items
用户库:users、profiles
商品库:products、skus
营销库:coupons、activities

字段垂直拆分:

orders:高频核心字段
order_details:低频详情、大字段

优点:

  1. 边界清晰;
  2. 故障域隔离;
  3. 权限和安全更容易;
  4. 大字段不拖慢核心表;
  5. 适合微服务治理。

局限:

  1. 不能解决单业务无限增长;
  2. 跨库 JOIN 变复杂;
  3. 需要服务边界治理;
  4. 分布式事务问题出现。

28.3 水平分片

水平分片把同一张表按规则拆到多个库或表:

orders_00 -> db_00
orders_01 -> db_01
orders_02 -> db_02
orders_03 -> db_03

常见分片方式:

方式 规则 特点
Hash user_id % N 分布均匀,扩容迁移多
Range 按时间或 ID 区间 便于归档,易热点
一致性 Hash 环形映射 减少迁移
基因法 ID 中嵌入分片基因 避免二次查询
查表 保存分片映射表 灵活,映射表自身要高可用
地理位置 按城市 / 区域 本地化,跨区查询复杂

28.4 分片键选择

分片键应满足:

  1. 大多数核心查询都带它;
  2. 写入分布均匀;
  3. 业务生命周期内稳定;
  4. 不容易变更;
  5. 能表达数据归属;
  6. 与交易边界一致。

常见选择:

业务 通常分片键
电商订单 user_id 或 seller_id
支付流水 payment_no / merchant_id
聊天消息 conversation_id
IoT 时序 device_id + time
SaaS 多租户 tenant_id
日志检索 time + source

矛盾场景:订单既要按买家查,又要按卖家查。常见方案:

  1. 以买家为主分片;
  2. 卖家订单异步同步到卖家维度表;
  3. 后台运营查询走分析库;
  4. 全局索引服务;
  5. 双写投影,接受同步延迟。

不要把两个维度都作为强一致实时查询路径。

28.5 全局 ID

分片后自增 ID 不能直接保证全局唯一。

常见方案:

方案 说明 风险
号段模式 数据库批量发号 中心依赖
雪花 ID 时间 + 机器 + 序列 时钟回拨
UUID 全局唯一 空间大、无序
Redis INCR 高性能 持久化和中心依赖
分片前缀 ID 带分片信息 泄露拓扑

推荐:

内部主键:趋势递增 BIGINT
业务单号:带日期、类型、机器位或随机位
对外 ID:随机 public_id

雪花 ID 必须处理时钟回拨、机器分配、监控和告警。

28.6 跨片查询

按非分片键查询:

SELECT *
FROM orders
WHERE order_no = '20260825000001';

如果分片键是 user_id,系统需要先知道 order_no 在哪个分片。

方案:

  1. 订单号嵌入用户 ID 基因;
  2. 保存全局索引表;
  3. 使用搜索引擎;
  4. 异步写投影表;
  5. 广播所有分片;
  6. 限制后台广播查询。

广播查询代价:

1 条查询 -> N 个分片 -> N 份执行计划 -> N 份网络往返

必须设置超时、并发限制和结果归并上限。

28.7 跨片事务

尽量避免跨片事务。设计原则:

  1. 一个交易边界内只操作一个分片;
  2. 其他动作使用本地状态表;
  3. 事务消息驱动最终一致;
  4. 幂等消费;
  5. 定时对账;
  6. 人工补偿入口。

本地消息表示例:

CREATE TABLE local_message_outbox (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    message_id VARCHAR(64) NOT NULL,
    aggregate_type VARCHAR(32) NOT NULL,
    aggregate_id VARCHAR(64) NOT NULL,
    payload JSON NOT NULL,
    status VARCHAR(16) NOT NULL,
    retry_count INT UNSIGNED NOT NULL DEFAULT 0,
    next_retry_at DATETIME(3) NOT NULL,
    created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    PRIMARY KEY (id),
    UNIQUE KEY uk_message_id (message_id),
    KEY idx_status_next_retry (status, next_retry_at)
) ENGINE=InnoDB;

业务写入和 outbox 同库同事务,发送器读取 outbox 投递消息。

28.8 中间件路由

常见方式:

方式 代表 特点
客户端 SDK ShardingSphere-JDBC 少一层代理,语言绑定
代理层 ShardingSphere-Proxy、MyCat 语言无关,链路长
服务层路由 微服务自行路由 灵活,业务侵入
云原生分发 云厂商分布式数据库 托管,锁定能力不同

中间件要评估:

  1. SQL 兼容度;
  2. 事务语义;
  3. 读写分离;
  4. 连接数;
  5. 元数据管理;
  6. 监控追踪;
  7. DDL 支持;
  8. 社区和版本。

28.9 迁移总体流程

1. 明确目标:容量、性能、成本、安全、运维;
2. 设计目标结构和路由;
3. 评估 SQL 和事务改造成本;
4. 搭建目标集群;
5. 全量迁移;
6. 增量同步;
7. 数据校验;
8. 灰度读;
9. 双写或写切换;
10. 观察和回滚窗口;
11. 下线旧链路;

每个阶段都要有 checkpoints、指标和回滚方案。

28.10 全量与增量迁移

全量

按分片键或主键分片读取:

SELECT *
FROM orders
WHERE id > :last_id
ORDER BY id
LIMIT 1000;

要求:

  1. 任务可断点续跑;
  2. 每批记录水位;
  3. 目标端幂等插入;
  4. 限速;
  5. 失败可重试。

增量

使用 Binlog CDC:

旧库 Binlog -> 解析 -> 按新分片键写入目标库

需要处理:

  1. DDL;
  2. INSERT / UPDATE / DELETE;
  3. 事务边界;
  4. 乱序;
  5. 重复;
  6. 位点持久化;
  7. 延迟;
  8. 大事务;
  9. 主从切换。

28.11 双写切流

双写流程:

阶段一:写旧库,CDC 同步新库;
阶段二:写旧库为主,同时写新库,新库失败记录;
阶段三:校验一致,读灰度到新库;
阶段四:写新库为主,反向同步旧库;
阶段五:确认稳定,下线旧库;

关键点:

  1. 以旧库为准或以新库为准必须唯一;
  2. 双写失败要有补偿队列;
  3. 不能让两个都成功的路径分叉;
  4. 写请求带版本或更新时间;
  5. 删除和状态回退要能同步;
  6. 保留反向回滚链路。

28.12 数据校验

校验维度:

类型 示例
结构 表、列、索引、字符集
数量 总行数、分片行数
聚合 SUM 金额、COUNT 状态
抽样 主键详情对比
业务 订单和明细关联
时效 增量延迟
约束 唯一键冲突
性能 核心接口耗时

示例:

SELECT
  COUNT(*) AS row_count,
  SUM(total_amount) AS amount_sum,
  MAX(updated_at) AS max_updated
FROM orders
WHERE created_at >= '2026-08-01';

两边同口径执行,不能直接比较未对齐时间窗口的数据。

28.13 切换与回滚

切换前检查:

1. 增量延迟低于阈值;
2. 数据校验通过;
3. 核心接口压测通过;
4. 回滚开关可用;
5. 监控和告警就绪;
6. 值班人员确认;
7. 业务低峰窗口确认;

回滚方式:

  1. 配置中心切回旧库;
  2. 新库写入反向同步旧库;
  3. 修复分叉数据;
  4. 冻结新增数据;
  5. 人工校验后再放量。

回滚必须在设计时验证过,不能在事故现场第一次尝试。

本章小结

分库分表先从业务边界和字段垂直拆分开始,再按稳定高频维度水平分片。分片键决定查询、事务和扩容成本,跨片查询应通过投影、基因或全局索引治理。数据迁移必须包含全量、增量、校验、灰度、切换和反向回滚,不能只做一次性复制。

思考题

  1. 为什么不能只用行数判断是否分表?
  2. 分片键应该满足哪些条件?
  3. 买家维度和卖家维度查询冲突时如何设计?
  4. 双写切流如何避免数据分叉?
  5. 写一个分片迁移项目的里程碑和回滚清单。