摘要
从 MySQL 迁移到 TiDB,尤其在分库分表场景下,面临数据合并、增量同步、一致性校验和零停机切换等多重挑战。本文提供完整的平滑迁移方案,覆盖分库分表合并策略、全量增量迁移流程、多维度数据校验方法、双写验证/灰度/全切三阶段切换策略和回滚预案,帮助企业实现安全可控的数据库迁移。
本文适合谁:正在规划或执行 MySQL → TiDB 迁移的 DBA、后端架构师、技术负责人,以及需要处理分库分表合并场景的团队。
一、分库分表合并策略
1.1 常见分库分表模式
| 模式 | 示例 | 合并复杂度 | 说明 |
|---|---|---|---|
| 按 ID Hash 分表 | `orders_0` ~ `orders_15` | 低 | 结构一致,直接合并 |
| 按用户分库 | `db_user0` ~ `db_user7` | 低 | 按分片键合并 |
| 按时间分表 | `orders_2024q1` ~ `orders_2024q4` | 低 | 追加合并 |
| 混合分片 | Hash + 时间组合 | 高 | 需多级路由 |
| 异构分片 | 不同表结构 | 极高 | 需字段映射和 ETL |
1.2 DM 路由合并配置
# dm-task.yaml 分库分表合并配置
name: "merge-sharded-orders"
task-mode: "all" # 全量 + 增量
shard-mode: "pessimistic" # 悲观模式(默认推荐)
mysql-instances:
- source-id: "shard-0"
mydumper-config:
extra-args: "--no-locks"
loader-config:
batch-size: 100000
- source-id: "shard-1"
- source-id: "shard-2"
routes:
merge-orders:
schema-pattern: "shard_*"
table-pattern: "orders"
target-schema: "business"
target-table: "orders"
filters:
ignore-temp-tables:
schema-pattern: "shard_*"
table-pattern: "tmp_*"
action: "Ignore"
# 分片键映射规则(如源端分片键与目标端不同)
column-mappings:
mapping-order-id:
schema-pattern: "shard_*"
table-pattern: "orders"
source-column: "order_no"
target-column: "order_id"
expression: "order_no"
1.3 分片键处理注意事项
| 问题 | 解决方案 |
|---|---|
| 源端自增 ID 冲突 | 使用 `AUTO_RANDOM` 替代 `AUTO_INCREMENT` |
| 分片键变更 | 迁移期间冻结分片键修改,迁移后统一处理 |
| 数据倾斜 | TiDB 天然分布式,不存在热点问题 |
| 关联查询跨分片 | 合并后 JOIN 无需跨库,性能大幅提升 |
二、数据迁移流程
2.1 总体流程
┌─────────────────────────────────────────────────────────────┐
│ MySQL → TiDB 迁移全流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐ │
│ │ 1.评估 │──►│ 2.全量 │──►│ 3.增量 │──►│ 4.校验 │ │
│ │ │ │ 迁移 │ │ 同步 │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘ └────┬─────┘ │
│ │ │ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ▼ │ │
│ │ 7.回滚 │◄──│ 6.全量 │◄──│ 5.灰度 │◄── 验证通过 │ │
│ │ 预案 │ │ 切换 │ │ 验证 │ │ │
│ └─────────┘ └─────────┘ └─────────┘ │ │
│ │
└─────────────────────────────────────────────────────────────┘
2.2 全量迁移
使用 TiDB DM 的全量迁移能力,自动处理分库分表合并:
# 部署 DM 集群
tiup dm deploy dm-prod v7.5.0 ./dm-topo.yaml
# 启动迁移任务
tiup dmctl --master-addr 10.0.1.1:8261 start-task ./dm-task.yaml
# 查看迁移状态
tiup dmctl --master-addr 10.0.1.1:8261 query-status merge-sharded-orders
2.3 增量同步
全量迁移完成后,DM 自动切换到增量同步模式,持续从 MySQL binlog 捕获变更并写入 TiDB:
| 监控项 | 命令/SQL | 正常阈值 |
|---|---|---|
| 同步延迟 | `query-status` | < 5s |
| binlog 位点 | `SHOW MASTER STATUS` | 持续推进 |
| 错误日志 | DM 日志 | 无 ERROR |
| 数据校验差异 | sync-diff-inspector | 差异行数 = 0 |
三、数据校验方法
3.1 多层校验体系
| 校验层级 | 方法 | 工具 | 适用阶段 |
|---|---|---|---|
| L1 行数 | COUNT(*) | DM 内置 | 全量完成后 |
| L2 Checksum | 表级 checksum | DM 内置 | 增量同步期间 |
| L3 字段比对 | 全字段逐行比对 | sync-diff-inspector | 切换前 |
| L4 业务逻辑 | 业务函数比对 | 自研自动化脚本 | 切换前/后 |
3.2 sync-diff-inspector 配置
# diff-config.toml
[task]
check-thread-count = 16
export-fix-sql = true
check-tables = ["business.orders", "business.order_items"]
[data-sources]
[data-sources.mysql]
host = "10.0.1.100"
port = 3306
user = "checker"
password = "***"
route-rules = ["merge-rules"]
[data-sources.tidb]
host = "10.0.1.200"
port = 4000
user = "checker"
password = "***"
[routes]
[routes.merge-rules]
schema-pattern = "shard_*"
table-pattern = "orders"
target-schema = "business"
target-table = "orders"
[rules]
schema-rules = ["merge-rules"]
table-rules = ["merge-rules"]
# 执行校验
./sync_diff_inspector --config diff-config.toml
3.3 校验结果判定
| 结果 | 含义 | 处理 |
|---|---|---|
| PASS | 数据完全一致 | 可进入切换准备 |
| 差异行 < 0.01% | 存在极少量差异 | 分析差异原因,确认为增量延迟 |
| 差异行 > 0.01% | 存在明显差异 | 暂停迁移,排查问题 |
四、业务切换策略
4.1 三阶段切换模型
阶段一:双写验证 阶段二:灰度切换 阶段三:全量切换
MySQL ──────► 业务 MySQL ──► 20%流量 MySQL ──► 下线
▲ │ ▲ │
│ TiDB ◄── 业务 │ TiDB ◄─ 80%流量
│ │ │ │
写入双写 读取对比 读取切换 写入切换
校验一致性 响应时间 监控指标 全流量TiDB
4.2 阶段一:双写验证
| 步骤 | 操作 | 验证点 |
|---|---|---|
| 1 | 应用改造为双写(MySQL + TiDB) | 写入链路正常 |
| 2 | 读取仍走 MySQL,后台对比 TiDB 数据 | 数据一致性 |
| 3 | 持续 1-7 天(根据业务频率) | 无数据差异 |
| 4 | 对比 DM 增量同步与双写数据 | 无冲突 |
4.3 阶段二:灰度切换
| 步骤 | 操作 | 验证点 |
|---|---|---|
| 1 | 读取流量 10% → 20% → 50% 切换到 TiDB | 响应时间无退化 |
| 2 | 监控 TiDB 负载和 MySQL 负载变化 | TiDB 负载健康 |
| 3 | 业务方确认功能正常 | 无业务异常 |
| 4 | 写入仍走 MySQL(或开始灰度写入) | 数据一致 |
4.4 阶段三:全量切换
切换检查清单:
- [ ] 增量同步延迟 < 1s,持续 1 小时以上
- [ ] sync-diff-inspector 差异行数 = 0
- [ ] 灰度期间 TiDB P99 延迟 ≤ MySQL P99 延迟
- [ ] TiDB 集群健康检查全部通过
- [ ] 回滚方案验证通过
- [ ] 业务方确认切换窗口
- [ ] DBA 团队在线值守
- [ ] 监控告警配置就位
五、回滚方案
5.1 回滚触发条件
| 条件 | 阈值 | 处理 |
|---|---|---|
| 数据不一致 | 差异行 > 0.1% | 立即回滚 |
| 性能严重劣化 | P99 > MySQL 2 倍 | 立即回滚 |
| TiDB 集群故障 | 节点不可用 > 2 个 | 立即回滚 |
| 业务异常 | 业务错误率 > 0.5% | 立即回滚 |
5.2 回滚步骤
| 步骤 | 操作 | 预估耗时 |
|---|---|---|
| 1 | 停止双写,仅写 MySQL | < 1min |
| 2 | 读取流量切换回 MySQL | < 2min |
| 3 | 验证 MySQL 数据完整性 | 5-10min |
| 4 | 通知业务方恢复确认 | 5min |
| 5 | 排查问题根因,修复后重新迁移 | 视问题而定 |
5.3 回滚保障机制
- 数据安全:切换前 MySQL 数据不删除,保留至少 30 天
- 配置热切换:通过配置中心(如 Nacos/Apollo)秒级切换数据源
- 连接池预加载:应用同时维护 MySQL 和 TiDB 连接池,避免连接建立延迟
- 灰度网关:通过 API Gateway 或 Service Mesh 控制流量分配
六、FAQ
Q1:分库分表迁移到 TiDB 后,原有的 ShardingSphere 中间件还需要保留吗? A1:不需要。TiDB 原生支持分布式事务和水平扩展,合并后不再需要分库分表中间件。建议迁移完成后逐步移除中间件层,简化架构。
Q2:迁移过程中如何处理自增 ID 冲突? A2:DM 在合并分表时会自动处理 ID 冲突。对于新写入的 ID,建议 TiDB 侧使用 `AUTO_RANDOM`(TiDB 独有),避免写入热点。创建表时使用 `AUTO_RANDOM(5)` 即可。
Q3:双写阶段出现数据不一致如何处理? A3:首先判断不一致原因:如果是由增量同步延迟导致,等待追平即可;如果是由业务逻辑差异导致(如 TiDB 和 MySQL 的默认值不同),需在 DM 配置中添加列映射规则进行修正。极端情况下,可使用 sync-diff-inspector 的 `fix-sql` 功能自动修复。
Q4:从 MySQL 5.7 迁移到 TiDB 和从 MySQL 8.0 迁移有何不同? A4:TiDB 同时兼容 MySQL 5.7 和 8.0 的协议特性。MySQL 8.0 新增的 CTE(公用表表达式)、窗口函数、JSON 函数等 TiDB 均已支持。主要差异在于部分 8.0 特有语法(如 `LATERAL JOIN`)在 TiDB 早期版本可能不支持,建议使用 TiDB v7.0+ 以获得最佳兼容性。
总结
MySQL → TiDB 平滑迁移的核心在于分阶段、可回滚、严校验:
- 分库分表合并:利用 DM 路由规则完成多源到单表的自动合并
- 全量增量分离:先全量迁移再增量同步,确保迁移期间业务不中断
- 多维度校验:从行数到全字段逐级校验,确保数据零丢失
- 三阶段切换:双写验证 → 灰度 → 全量,每阶段可独立回滚
- 回滚预案:任何阶段出现异常都能在分钟级恢复
下一步行动
- 试用 TiDB DM:通过 TiUP 快速部署 TiDB + DM 集群,30 分钟内完成首次分库分表合并迁移实验。
- 获取定制迁移方案:填写 TiDB 迁移咨询表单,PingCAP 架构师团队将根据您的分库分表拓扑和业务规模提供定制化迁移方案(含预估工期和资源规划)。
- 下载迁移工具包:访问 TiDB 工具集下载页,获取 DM、sync-diff-inspector、Lightning 完整工具链。