ALTER TABLE ... ADD INDEX 仍可能卡住业务,因即使 ALGORITHM=INPLACE, LOCK=NONE 也仅避免锁表,不规避全表扫描、B+树分裂、内存排序溢出、Row Log 积压等资源争用,导致 CPU/IO 打满、查询响应变慢、连接堆积。

不能真正“零宕机”,但可以做到业务无感知的写入阻塞,前提是用对方法、控住资源、避开陷阱。
为什么 ALTER TABLE ... ADD INDEX 仍可能卡住业务?
即使在 MySQL 5.7+ 使用 ALGORITHM=INPLACE, LOCK=NONE,也只保证“不锁表”,不代表“不抢资源”。一亿行数据构建联合索引时,会触发:
- 全表扫描(读取主键页 + 所有二级索引字段值)
- 大量磁盘随机写(B+ 树页分裂、填充)
- 内存排序溢出到磁盘(
tmpdir占满或sort_buffer_size不足时) - Row Log 积压(高并发 DML 下,最后阶段应用日志耗时飙升)
这些行为会让 mysqld 进程 CPU 和 IO 持续打满,导致正常查询响应变慢、超时、连接堆积——业务“没挂”,但“卡得像挂了”。
必须用 pt-online-schema-change 的三个硬场景
当出现以下任一情况,原生 Online DDL 就不该上:
- 主从延迟已 > 30s,且无法暂停写入(
pt-osc可自动限流) - 表上有长事务(
information_schema.innodb_trx中trx_started超过 60s),原生 DDL 会等它结束 - 磁盘剩余空间 pt-osc 把压力分散到多个小批次,更可控
执行示例:
pt-online-schema-change \ --alter "ADD INDEX idx_user_city_age (user_id, city, age)" \ --chunk-size=10000 \ --max-load="Threads_running=25" \ --critical-load="Threads_running=50" \ --check-interval=5 \ --execute \ D=users,t=user_profiles
关键参数:--chunk-size 控制每批复制行数;--max-load 是安全阀,超阈值自动暂停。
CREATE INDEX CONCURRENTLY 在 MySQL 里不存在
别被 PostgreSQL 或 MariaDB 文档误导。MySQL 原生不支持 CONCURRENTLY 关键字,如下写法会直接报错:
CREATE INDEX CONCURRENTLY idx_x ON t (a,b); -- ERROR 1064 (42000)
某些 DBA 会手动模拟“并发”:建临时表 → 加索引 → 导数据 → 切换。但该方式风险极高:
- 切换瞬间(
RENAME TABLE)是原子操作,但需持有 MDL 写锁,若此时有长查询,锁等待会堆积 - 中间状态无回滚能力,出错只能人工恢复
- 无法自动处理主从延迟、binlog 位点漂移等问题
所以,除非你有完整脚本+充分压测+值班兜底,否则不建议手写这套流程。
最容易被忽略的坑:联合索引列顺序与覆盖性
加完索引发现查询没走?大概率栽在这两点:
-
WHERE city = ? AND age > ?用了(city, age)索引,但SELECT *导致大量回表——而亿级表一次回表就是一次随机 IO。应优先考虑覆盖索引,比如(city, age, user_name, avatar_url) - 把低选择性列放前面,例如
(status, user_id),status只有 0/1 两个值,索引区分度极差,优化器大概率弃用
验证是否生效,别只看 EXPLAIN,要查 performance_schema.events_statements_summary_by_digest 中该 SQL 的 rows_examined 是否显著下降。


















