小表用ALTER TABLE ADD UNIQUE,大表(如500万行以上)优先用pt-online-schema-change;加唯一索引前必须清理重复数据,否则失败。

MySQL 5.7加索引到底该用ALTER还是pt-osc?
直接结论:小表(SELECT COUNT(*) ALTER TABLE ... ADD INDEX 配合 ALGORITHM=INPLACE, LOCK=NONE;大表(千万级以上)、需强一致性、或已知存在重复数据的唯一索引,必须用 pt-online-schema-change。
MySQL 5.7 默认支持 Online DDL,但“在线”不等于“无影响”——Prepare/Commit 阶段仍需短暂 MDL 锁,若此时有未提交的长事务,整个 ALTER 就会卡在 Waiting for table metadata lock。而 pt-osc 的锁只发生在最后 RENAME 瞬间(毫秒级),对业务更友好。
执行前必须检查的5个硬性条件
pt-osc 不是拿来就跑的命令,漏掉任一条件都会失败或引发数据异常:
- 主库
binlog_format必须为ROW(MIXED或STATEMENT会导致增量同步丢失) - 目标表必须有主键或唯一索引(否则无法分块拷贝,工具直接退出)
- 确认磁盘剩余空间 ≥ 原表大小 × 2(含新表 + 触发器日志临时表)
- 禁用复制过滤规则(如
replicate-ignore-db),或显式加--no-check-replication-filters - 若表有外键,必须加
--alter-foreign-keys-method=auto,但更推荐先DROP FOREIGN KEY再操作
添加普通索引和唯一索引的关键区别
行为差异直接影响结果正确性,不能混用:
- 普通索引(
ADD INDEX idx_col (col)):pt-osc全量拷贝走INSERT IGNORE,不校验重复,性能快,适合快速上线 - 唯一索引(
ADD UNIQUE INDEX uk_col (col)):pt-osc**不校验全表唯一性**,遇到重复值直接覆盖(后写生效),可能丢数据!必须提前执行:SELECT col, COUNT(*) FROM t GROUP BY col HAVING COUNT(*) > 1;
并清理重复行 - 如果只是想“加索引防误插”,但允许历史重复存在,可先加普通索引,再通过应用层逻辑控制新增数据
一条能跑通的生产命令模板
以下命令已在 MySQL 5.7.36 + Percona Toolkit 3.5+ 验证可用,参数按生产环境保守设置:
pt-online-schema-change \ --alter "ADD UNIQUE INDEX uk_email (email)" \ --execute \ D=test,t=users \ --chunk-size=500 \ --max-load="Threads_running=20" \ --critical-load="Threads_running=40" \ --recursion-method=none \ --set-vars="innodb_lock_wait_timeout=1" \ --no-version-check
说明:
-
--chunk-size=500:避免单次拷贝太久阻塞 I/O,尤其机械盘或高负载实例 -
--max-load和--critical-load是熔断开关,防止雪崩;值需根据你实例的SHOW STATUS LIKE 'Threads_running'常态值调整 -
--recursion-method=none:跳过从库延迟检测(若你没配从库或确定不关心) - 执行后务必检查输出末尾是否出现
Successfully altered `test`.`users`.,而非Operation stopped
最后提醒:pt-osc 过程中触发器会带来额外开销,高并发写入场景下,观察 SHOW PROCESSLIST 中是否有大量 Trigger 状态线程;若延迟明显,应降低 --chunk-size 或错峰执行。


















