优雅回滚的核心是提前设计可逆路径、控制锁影响范围、明确数据兼容边界;DDL不支持事务回滚,需人工执行反向操作,且依赖结构与数据的双向兼容性。

ALTER TABLE 不是事务,回滚不是按 Ctrl+Z 那么简单。真正要“优雅”回滚,核心在于**提前设计可逆路径 + 控制锁影响范围 + 明确数据兼容边界**,而不是等报错后再找补。
DDL 变更本身不支持事务回滚
执行 ALTER TABLE 后无法用 ROLLBACK 撤销——它不参与事务控制,START TRANSACTION 对它完全无效。所谓“回滚”,本质是人工执行反向 DDL(如删字段、改类型),但前提是:旧数据能塞得进新结构,或新结构没破坏旧语义。
-
DROP COLUMN后数据永久丢失,没有“撤销”这回事 -
MODIFY COLUMN VARCHAR(255) → VARCHAR(50)若已有超长值,会被截断;改回去也恢复不了原始内容 -
ADD COLUMN ... DEFAULT 'x'会触发全表更新(即使ALGORITHM=INPLACE),中途失败可能留下半成品状态
加字段/改默认值这类“安全变更”才能用 LOCK=NONE
只有部分变更支持在线无锁(MySQL 5.6+):ADD COLUMN、ADD INDEX、ALTER COLUMN ... SET DEFAULT 等。但必须显式指定参数,否则仍走拷表流程。
- 正确写法:
ALTER TABLE t ADD COLUMN status TINYINT DEFAULT 0, ALGORITHM=INPLACE, LOCK=NONE - 错误写法:
ALTER TABLE t ADD COLUMN status TINYINT DEFAULT 0—— 默认可能锁表,尤其在大表上 -
LOCK=NONE失败时 MySQL 会自动降级为LOCK=SHARED或报错,需检查SHOW WARNINGS - 即使支持
INPLACE,DDL 过程中若遇到长事务(SELECT FOR UPDATE或未提交的 DML),仍会被 MDL 锁阻塞
线上大表变更必须绕过原生 ALTER TABLE
超过百万行的表,ALTER TABLE 时间不可控,极易引发应用超时、连接池耗尽、500 报错。此时应放弃原生命令,改用影子表方案。
-
pt-online-schema-change:依赖触发器同步,需确保主从延迟可控、禁止在变更期间删触发器 -
gh-ost:基于 binlog 解析,不依赖触发器,但要求 binlog_format=ROW,且变更期间不能停写 - 两者都不修改原表结构,而是新建影子表 + 逐步迁移数据 + 原子切换(
Rename),读写几乎不受影响 - 代价是增加磁盘占用(临时表)、延长总耗时(分批 copy)、引入新故障点(如触发器性能抖动)
回滚脚本必须和上线脚本一起评审、一起发布
上线前写的回滚 SQL 不等于“能跑通”,它必须经过验证:字段删掉后业务逻辑是否崩?索引去掉后慢查询是否复现?存储过程改回去会不会因语法差异报错?
- 回滚脚本里不能只写
DROP COLUMN,还要包含:字段是否被代码引用、是否作为外键、是否在视图/函数中出现 - 涉及数据迁移的变更(如拆分字段、类型转换),回滚脚本必须包含反向清洗逻辑,不能假设“原样还原”
- 把回滚 SQL 和上线 SQL 放进同一 Git 提交,用 CI 自动校验语法,并在预发环境实测执行耗时
- 严禁“先上线,回头再补回滚方案”——这时候往往已经丢了备份、忘了旧结构、或者数据已被二次修改


















