pt-online-schema-change能绕过MDL锁阻塞,因其不直接执行ALTER TABLE,而是通过影子表、触发器和增量同步模拟变更,仅在启停时短暂加MDL读锁。

为什么pt-online-schema-change能绕过MDL锁阻塞
因为pt-online-schema-change根本不直接执行ALTER TABLE,而是用影子表+触发器+数据同步的方式“模拟”结构变更:它创建新表、把原表数据逐步拷贝过去、在拷贝期间用触发器捕获原表的增删改并重放至新表。整个过程原表始终可读写,Server 层无需对原表加 MDL 写锁——所以不会被长事务或SELECT阻塞。
但要注意:它仍需在启动和结束阶段短暂获取原表的 MDL 读锁(用于原子切换),此时若恰有未提交事务正在查原表,仍可能卡住几秒。这不是 bug,是设计上的最小必要锁。
执行前必须确认的三项检查
漏掉任意一项都可能导致数据不一致或工具中断失败:
- 目标表必须有主键或唯一非空索引——否则无法按行精确同步,
pt-online-schema-change会直接报错退出 - 确保
binlog_format = ROW且binlog_row_image = FULL——触发器依赖 binlog 解析变更,语句级日志无法支持 - 检查磁盘空间:影子表 + 原表 + 临时日志至少需要 2 倍原表空间;
df -h看/var/lib/mysql所在分区剩余空间是否 ≥2 × (data_length + index_length)(可从information_schema.TABLES查)
典型命令与关键参数含义
以“给orders表加is_archived TINYINT字段”为例:
pt-online-schema-change \ --host=localhost \ --user=dba \ --password=xxx \ --alter "ADD COLUMN is_archived TINYINT DEFAULT 0" \ D=shop,t=orders \ --execute \ --chunk-size=1000 \ --max-load="Threads_running=25" \ --critical-load="Threads_running=50" \ --check-interval=5
--execute是真正执行的开关(不加则只预演);--chunk-size控制每次拷贝行数,大表建议 500–2000;--max-load和--critical-load是熔断机制——当SHOW STATUS LIKE 'Threads_running'超阈值时自动暂停,避免拖垮数据库;--check-interval决定每几秒检查一次负载。
别用--dry-run代替--execute测试,它不建触发器、不跑数据同步,无法暴露触发器权限或 binlog 配置问题。
常见失败场景与对应处理
工具报错后不要立刻重试,先查pt-osc生成的日志文件(默认在/tmp/pt-online-schema-change*):
-
Cannot drop trigger ... because it does not exist:说明上一次执行异常中断,残留了触发器但元数据没清理干净。手动删掉pt_osc_*开头的触发器:DROP TRIGGER IF EXISTS pt_osc_shop_orders_ins; -
Table has no primary key:不是警告,是硬性拒绝。必须先加主键或唯一索引,不能跳过 -
Lost connection to MySQL server during query:大概率是wait_timeout或interactive_timeout太短(默认 28800 秒),导致长连接被服务端断开。临时调高:SET GLOBAL wait_timeout = 86400; - 切换表瞬间出现短暂写入失败:这是正常现象,
RENAME TABLE那 0.1 秒内新旧表名交替,客户端若恰好发请求会收到ERROR 1146 (42S02): Table doesn't exist。业务层需容忍这种瞬时错误
真正难处理的是触发器冲突——比如你手动在原表上加了同名触发器,或用了gh-ost又切回pt-osc,残留逻辑没清干净。这种必须人工核对information_schema.TRIGGERS,一个一个删。


















