MySQL的ALTER TABLE ... RENAME在5.7及之前会全表锁,8.0虽优化为MDL锁但仍需排他锁,存在阻塞风险;应采用CREATE+INSERT+DROP分步迁移,并严格控制切换节奏与回滚预案。

ALTER TABLE … RENAME 会锁表,线上不能直接用
MySQL 的 ALTER TABLE ... RENAME 在 5.7 及之前版本中是**全表 DDL 锁**,执行期间阻塞所有读写;8.0 虽优化为元数据锁(MDL)级别,但 rename 操作仍需获取排他 MDL,意味着新旧表名切换瞬间会卡住正在访问原表的事务。业务没感知就挂了,不是“慢”,是直接超时或连接池耗尽。
- 别信“只是改个名字很快”——锁等待链一旦形成,会把上游服务拖垮
- 如果表上有长事务、慢查询、或被
SELECT ... FOR UPDATE占着,rename 会一直等,直到超时(默认lock_wait_timeout=31536000,即一年) - 主从延迟敏感场景下,rename 在主库执行后,从库重放时若遇到同名表冲突(比如你先删了旧表再 rename),可能直接中断复制
用 CREATE + INSERT + DROP 分步迁移,控制流量切换节奏
本质是绕开 DDL 锁,把“改名”拆成可灰度、可观测、可回滚的三步:建新表 → 导数据 → 切流量 → 清旧表。关键不在快,而在可控。
- 新表必须用
CREATE TABLE ... LIKE复制结构(含索引、约束、字符集),不能只靠SHOW CREATE TABLE手动建——容易漏STATS_PERSISTENT或隐藏的ROW_FORMAT - INSERT 用
INSERT INTO new_table SELECT * FROM old_table时,务必加LIMIT和SLEEP,避免单次操作太久触发max_execution_time或主从 IO 压力飙升;推荐分批次,例如每 1w 行 sleep 0.1 秒 - 切流量前,确认所有应用配置、SQL 模板、ORM 映射、监控告警里引用的都是新表名;特别注意
INFORMATION_SCHEMA查询、慢日志分析脚本、备份策略中的表名硬编码
RENAME 操作本身要放在低峰期 + 加超时 + 配合 kill 脚本
即便做了分步迁移,最终那个 RENAME TABLE old TO tmp, new TO old 原子切换,仍是风险点。它不耗时,但对锁敏感。
- 必须选凌晨或业务低谷,且提前确认没有未提交事务在查/写该表(查
SELECT * FROM performance_schema.threads WHERE PROCESSLIST_INFO LIKE '%old_table%') - 执行命令加上
SET lock_wait_timeout = 5,避免无限等待;配合外部 shell 脚本,超时自动KILL对应线程(查PROCESSLIST_ID后KILL CONNECTION) - 不要用
RENAME TABLE a TO b, c TO a这种双 rename —— MySQL 5.7 中若中途失败,可能留下中间状态(b 存在、a 消失、c 还在),8.0 支持原子性,但仍建议拆成两个独立 rename 并加检查
回滚方案不是“改回来”,而是留好退路和验证点
线上 rename 出问题,最危险的不是表没改成功,而是应用已发版、SQL 已切走、旧表被删,结果发现新表缺字段或索引失效。回滚不是倒带,是救火。
- 迁移前备份旧表结构和 100 行样本数据(
mysqldump --no-data+SELECT ... LIMIT 100),比全量备份快且够诊断 - 新表上线后,立刻跑一致性校验:对比
CHECKSUM TABLE(注意该函数在 8.0.26+ 已废弃,改用SELECT COUNT(*), MIN(id), MAX(id), SUM(CRC32(CONCAT(...)))) - 旧表别急着
DROP,至少保留 24 小时;期间所有日志、监控、错误追踪里出现的表名报错,优先查是否还有残留调用指向旧名
业务停机预案的核心不是写多少页文档,是每次 rename 前,你能 30 秒内说出:当前谁在连这张表、最后一条写入是什么时候、上一次校验结果在哪看、kill 脚本路径在哪、备份文件 md5 是否匹配。这些细节,比语法正确重要得多。


















