SQL 线上变更为何风险极高?

线上SQL变更会锁表或阻塞查询,因MySQL 5.7及更早版本ALTER TABLE默认表级拷贝,写入挂起;MySQL 8.0 RENAME COLUMN虽不拷贝数据但需SX元数据锁;PostgreSQL ADD COLUMN带DEFAULT非NULL时仍可能锁表;主从复制中DDL易引发延迟、不一致与隐性失败。

线上 SQL 变更为何会锁表或阻塞查询?

MySQL 5.7 及更早版本中,ALTER TABLE 大部分操作默认使用「表级拷贝」方式:先建新表、逐行复制数据、重建索引、再原子替换。整个过程原表持续可读,但写入(INSERT/UPDATE/DE

LETE)会被挂起,直到拷贝完成。如果表有 5000 万行,拷贝耗时 20 分钟,那这期间所有 DML 都在等待 waiting for table metadata lock

  • ADD COLUMNDROP COLUMNMODIFY COLUMN 在未指定 ALGORITHM=INPLACE 且不满足就地修改条件时,都会触发全表拷贝
  • 即使是 ADD INDEX,在 MySQL 5.6+ 虽支持 INPLACE,但若索引字段含 TEXT/BLOB 或字符集不一致,仍会退化为拷贝模式
  • pt-online-schema-changegh-ost 是绕过锁的常用方案,但它们本身会持续读写主表,对从库延迟、binlog 网络压力、主从 GTID 一致性都有隐性影响

为什么 ALTER TABLE ... RENAME COLUMN 在 MySQL 8.0 也需谨慎?

MySQL 8.0 引入了原生 RENAME COLUMN,语法简洁,但底层仍需重写表元数据并更新所有相关统计信息。它虽不拷贝数据,却会在执行瞬间获取 SX(Shared-Exclusive)元数据锁,阻塞并发的 SELECT ... FOR UPDATECREATE INDEX 等操作。

  • 如果该列被视图、存储过程、触发器、分区表达式引用,RENAME COLUMN 会直接失败,报错 ERROR 3780 (HY000)
  • 使用 information_schema.COLUMNS 或 ORM 自动生成建表语句的系统,可能因列名变更导致后续迁移脚本比对异常
  • 该操作不可回滚:一旦成功,旧列名彻底消失,应用若未同步更新,将立即抛出 Unknown column 'xxx' in 'field list'

PostgreSQL 的 ALTER TABLE ... ADD COLUMN 真的完全无锁?

PostgreSQL 对大多数 ADD COLUMN 操作确实只持 ACCESS EXCLUSIVE 锁极短时间(仅更新系统表),之后即可并发读写。但这不等于“零风险”:

  • 若新增列带 DEFAULT 值且非 NULL,PostgreSQL 11 之前会触发全表扫描补值(即“rewrite table”),锁表时间与数据量正相关
  • PostgreSQL 12+ 支持 ADD COLUMN ... DEFAULT NULL 无 rewrite,但若后续执行 ALTER COLUMN SET DEFAULT 并立刻 UPDATE 补值,等效于一次全表更新,极易引发长事务和 WAL 膨胀
  • pg_stat_progress_alter_table 视图可监控进度,但线上环境往往没开 track_activities,无法及时感知卡住

DDL 变更如何意外破坏主从一致性?

MySQL 主从复制中,ALTER TABLE 属于 DDL,其执行逻辑在 binlog 中记录为单条事件,但实际在从库回放时,仍要走一遍相同流程。问题常出现在:

  • 从库负载高或 IO 延迟大,导致 DDL 回放滞后,在此期间主库已执行后续 DML,造成从库短暂不一致甚至复制中断(如 Slave_SQL_Running_State: Waiting for table metadata lock
  • 使用 STATEMENT 格式 binlog 时,某些函数(如 NOW()UUID())在从库重放结果不同,而 DDL 中若嵌套这类表达式(如生成列定义),会导致主从表结构实质差异
  • ALTER TABLE ... ENGINE=InnoDB 这类操作在从库可能因磁盘空间不足静默失败,错误日志只记 Got error 12 from storage engine,不触发复制停止,隐患极深

线上 DDL 最危险的不是它慢,而是它“看起来快、实际副作用散落各处”——锁表现象易察觉,但元数据不一致、复制延迟毛刺、统计信息陈旧、ORM 缓存错位这些,往往在变更后数小时才集中爆发。