AFTER或FIRST触发全表扫描的本质是必须物理重建表:因InnoDB行存储依赖列顺序,调整字段位置会改变每行偏移量,故强制ALGORITHM=COPY,逐行拷贝重写,伴随全程MDL独占锁与密集I/O。

ALTER TABLE ... MODIFY COLUMN ... AFTER 触发全表扫描的本质原因
这不是“扫描是为了查数据”,而是 MySQL 在执行字段顺序调整时,必须重建整张表的物理结构。只要用了 AFTER 或 FIRST,无论表大小、是否加索引、有没有数据,都会触发 ALGORITHM=COPY(MySQL 5.6+ 默认行为),即创建新表、逐行拷贝、重建索引、重命名——这个过程天然伴随一次完整数据读取与写入,EXPLAIN 看不到,但 I/O 和锁表现就是“等效全表扫描”。
哪些 ALTER 操作会强制 COPY,哪些可以 INPLACE
关键看是否改变行格式或列存储位置:
-
MODIFY COLUMN/CHANGE COLUMN带AFTER或FIRST→ 必须COPY - 仅修改列默认值(
ALTER TABLE t ALTER COLUMN c SET DEFAULT 'x')→INPLACE - 仅增删列(不指定位置,即追加到末尾)→ MySQL 8.0+ 多数情况支持
INPLACE - 修改列类型(如
VARCHAR(100)→VARCHAR(200))→ 通常INPLACE,但若涉及字符集转换或长度溢出则退化为COPY
用 SHOW PROCESSLIST 或 performance_schema.table_io_waits_summary_by_table 可观察到该 DDL 期间对原表的持续 read 和新表的密集 write,这就是“隐式全表扫描”的真实痕迹。
为什么不能跳过 COPY 直接改元数据?
因为 MySQL 的 InnoDB 行记录是按列定义顺序紧凑存储的(row_format=COMPACT 或 DYNAMIC)。字段顺序变了,每行的偏移量(offset)就全乱了。没有中间层抽象,InnoDB 无法在不重排每条记录的前提下,仅靠更新数据字典就让旧数据“自动适配”新顺序。你看到的 AFTER 是逻辑描述,底层必须物理重写。
对比 PostgreSQL 就不同:它用 attrelid + attnum 分离逻辑顺序和物理布局,所以 ALTER TABLE ... ALTER COLUMN ... SET POSITION 是纯元数据操作,零 I/O。
线上大表改字段顺序的实操避坑点
别信“小表无所谓”——哪怕只有 10 万行,COPY 过程也会持 MDL_EXCLUSIVE 锁,阻塞所有 DML;若中途失败,回滚代价更高。
- 务必在低峰期执行,提前评估耗时:
SELECT COUNT(*) FROM t× 单行平均大小 ≈ 预估拷贝量 - 确认
innodb_file_per_table=ON,避免撑爆系统表空间 - 禁止在从库执行;主从延迟会因 COPY 操作陡增
- 替代方案优先考虑“不改顺序”:应用层兼容列名即可,物理顺序不影响 SQL 正确性
- 真需调整(如为后续添加生成列预留位置),可用
pt-online-schema-change或gh-ost实现无锁迁移
最常被忽略的一点:开发提需求说“把 created_at 调到 status 后面”,DBA 一执行就卡住业务——其实没人验证过这个顺序变更是否真被任何代码依赖。多数时候,字段顺序只影响 SELECT * 输出列序,而生产 SQL 应该永远显式写列名。


















