Tavio's blog Tavio's blog
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Tavio Zhang

努力学习的小码喽
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
    • 一、核心需求与方案设计原则
      • 1.1 核心需求
      • 1.2 方案核心思路
    • 二、全流程实施步骤
      • 步骤一:创建新表+业务双写,同步增量数据
      • 步骤二:主键分片+小批次迁移,处理历史数据
      • 步骤三:多维度数据校验,确保一致性
      • 步骤四:基于标记字段,平滑切换读写链路
      • 步骤五:停止双写+表名原子切换,收尾优化
    • 三、异常处理:UPDATE操作失败的数据追补
    • 四、方案优势与注意事项
      • 4.1 方案优势
      • 4.2 注意事项
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 双 Token 登录
  • 订单超时取消
  • 实践
Tavio
2022-06-21
目录

MySQL无感换表

在面临千万级数据量的换表需求。核心痛点在于:如何在不中断业务服务的前提下,保障数据零丢失、零不一致,同时避免迁移过程对线上业务性能造成显著影响。

# 一、核心需求与方案设计原则

# 1.1 核心需求

千万级数据换表的核心目标可概括为三点,也是方案设计的核心准则:

  • 业务无停机:迁移全程不中断线上读写服务,接口响应时间、并发处理能力维持正常水平,无感知切换。
  • 数据零风险:确保迁移过程中增量数据不丢失、历史数据迁移准确,新旧表数据完全一致,无脏数据、重复数据。
  • 低业务影响:迁移操作避免长时间锁表、占用过多数据库连接与IO资源,不影响线上正常业务的运行稳定性。

# 1.2 方案核心思路

针对上述需求,摒弃传统“锁表全量迁移”“直接重命名表”等风险方案,设计“临时表双写+主键分片分批迁移+原子切换”的全流程方案。核心逻辑是:先通过双写同步增量数据,再分批迁移历史数据,校验一致后平滑切换读写链路,最终完成原子换表,全程闭环保障数据与服务稳定。

# 二、全流程实施步骤

本方案以“旧表old_table→新表new_table”的换表场景为例,分五步落地,每一步均配套风险控制措施。

# 步骤一:创建新表+业务双写,同步增量数据

此步骤核心目标是解决“增量数据同步”问题,避免迁移历史数据时遗漏新产生的数据。

  1. 新表创建:根据业务需求创建新表new_table,确保表结构(字段、类型、索引、约束)与旧表一致或符合迭代需求。
  2. 业务代码双写改造:修改业务代码,实现对old_table和new_table的双写逻辑,并用数据库事务保证双写的原子性。即一次业务操作中,数据需同时写入两张表,事务提交后才算成功;若任一表写入失败,事务回滚,确保增量数据在两张表中完全一致。
  3. 双写灰度上线:双写逻辑上线后,先通过灰度流量验证功能稳定性,观察两张表的写入性能与数据一致性,无异常后全量上线。此时新表已开始同步所有增量数据,后续仅需聚焦历史数据迁移,规避“边迁移边丢数据”的风险。

# 步骤二:主键分片+小批次迁移,处理历史数据

千万级数据全量迁移易导致锁表、IO暴涨,影响线上业务,因此采用“主键ID分片+小批次迁移”策略,实现低侵入式历史数据迁移。

  1. 分片与批次设计:以主键ID作为分片键(主键为自增ID时最优,确保有序且无重复),设定每批次迁移数据量为1万条。
  2. 分批迁移逻辑:采用“游标式”迁移,每次迁移时以上一批次迁移的最大ID+1作为当前批次的起始ID,查询old_table中对应范围的数据,批量写入new_table。核心SQL示例如下:
-- 假设上一批次最大ID为last_max_id,当前批次迁移1万条
INSERT INTO new_table (col1, col2, ..., coln)
SELECT col1, col2, ..., coln
FROM old_table
WHERE id > last_max_id
ORDER BY id ASC
LIMIT 10000;
1
2
3
4
5
6
7
  1. 迁移优化措施:迁移过程中关闭new_table的非主键索引(迁移完成后重建,减少写入时的索引维护开销);避免在业务高峰期迁移,可选择凌晨等低峰时段执行;每批次迁移后暂停1-2秒,释放数据库资源,降低对线上IO的占用。

# 步骤三:多维度数据校验,确保一致性

数据迁移完成后,必须通过严格校验确认新旧表数据一致,避免迁移过程中出现数据丢失、篡改等问题,这是切换前的关键把关环节。

  1. 总行数对比:分别统计old_table和new_table的总行数,若行数一致,说明无明显的数据丢失;若行数不一致,需排查是否存在批次迁移遗漏、重复写入等问题。
-- 总行数校验
SELECT COUNT(*) AS old_count FROM old_table;
SELECT COUNT(*) AS new_count FROM new_table;
1
2
3
  1. 随机抽样全字段校验:总行数一致仅能说明数量无差,需通过随机抽样验证字段内容一致性。生成0到old_table最大ID之间的随机ID,抽取100-1000条数据(抽样数量根据数据量调整),对比新旧表对应ID的全字段内容。
-- 随机抽样校验(以MySQL为例)
SELECT * FROM old_table WHERE id IN (
  SELECT FLOOR(RAND() * (SELECT MAX(id) FROM old_table)) AS random_id
  FROM information_schema.tables LIMIT 100
)
UNION ALL
SELECT * FROM new_table WHERE id IN (
  SELECT FLOOR(RAND() * (SELECT MAX(id) FROM old_table)) AS random_id
  FROM information_schema.tables LIMIT 100
)
GROUP BY id HAVING COUNT(*) != 2;
1
2
3
4
5
6
7
8
9
10
11

若查询结果为空,说明抽样数据一致;若有结果返回,需逐字段排查差异原因。

# 步骤四:基于标记字段,平滑切换读写链路

数据校验通过后,需通过灰度切换策略,逐步将读写链路从old_table迁移到new_table,避免直接切换导致的业务抖动。方案采用数据库标记字段控制读写路由,标记值对应四种状态:

  • 标记0:读写均指向old_table(初始状态);
  • 标记1:读写均指向new_table(切换完成状态)。
  • 标记2:双写(新旧表均写入),读指向old_table(双写已上线状态,迁移历史数据阶段);
  • 标记3:双写,读指向new_table(切换过渡状态,验证new_table读性能与数据准确性);

切换流程:将标记从2更新为3,观察业务日志与监控,确认new_table读性能正常、无数据异常;稳定运行一个业务周期后,将标记从3更新为1,完成读写链路切换。整个过程无停机,可快速回滚(若出现问题,将标记切回2或0即可)。

# 步骤五:停止双写+表名原子切换,收尾优化

读写链路切换至new_table且稳定运行后,进行收尾操作,彻底完成换表。

  1. 停止双写:修改业务代码,删除对old_table的写入逻辑,仅保留对new_table的读写,上线后验证业务正常。
  2. 表名原子切换(可选):若业务依赖表名(如硬编码表名),可通过RENAME TABLE语句实现原子切换,避免修改代码。该操作是原子性的,执行过程中不会影响业务读写。
-- 原子切换表名,将new_table重命名为原表名,old_table备份
RENAME TABLE old_table TO old_table_backup, new_table TO old_table;
1
2
  1. 备份与清理:保留old_table_backup一段时间(如7天),期间若出现业务异常,可快速回滚;确认无问题后,删除备份表,释放存储空间。

# 三、异常处理:UPDATE操作失败的数据追补

迁移过程中,若old_table存在UPDATE操作,可能因批次迁移顺序、并发更新等问题导致new_table对应数据更新不及时,出现数据不一致。针对该场景,设计日志表追补方案:

  1. 日志表设计:创建update_log表,记录UPDATE操作的核心信息,仅需存储数据行ID,确保可追溯。
  2. 日志记录:修改业务代码,在执行old_table的UPDATE操作时,同步将操作记录写入update_log表;
  3. 数据追补:历史数据迁移完成后,读取update_log表中迁移期间的UPDATE记录,按ID批量同步至new_table,确保new_table数据与old_table一致。

# 四、方案优势与注意事项

# 4.1 方案优势

  • 无停机感知:全程通过双写、灰度切换实现平滑过渡,业务无中断,用户无感知。
  • 数据安全可控:事务双写、分批迁移、多维度校验、日志追补多重保障,确保数据零丢失、零不一致。
  • 低业务影响:小批次迁移、低峰时段执行、资源错峰分配,避免占用过多数据库资源,不影响线上业务性能。
  • 可回滚性:切换过程中可通过标记字段快速回滚,降低试错成本。

# 4.2 注意事项

  • 主键选择:优先使用自增主键作为分片键,若主键非自增,需选择有序、唯一的字段,避免分片遗漏。
  • 索引优化:迁移期间关闭new_table非主键索引,迁移完成后重建,提升迁移效率。
编辑 (opens new window)
#MySQL无感换表
上次更新: 2026/01/21, 19:29:14
前端实现长整型排序
Redis延时双删

← 前端实现长整型排序 Redis延时双删→

最近更新
01
订单超时取消
01-21
02
双 Token 登录
01-21
03
长短链接跳转
01-21
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式