MySQL数据库里跑着几千万甚至上亿行的大表,想加个字段或者改个索引,直接执行ALTER TABLE命令往往是一场灾难。这个操作会把整张表锁死,业务系统瞬间瘫痪,线上事故就是这么来的。Percona Toolkit里的pt-online-schema-change工具,就是专门解决这个问题的利器。它能做到在不锁表、不影响业务读写的前提下,完成表结构的在线变更。原理说起来不复杂:先创建一个符合新结构要求的空表,然后从原表里把数据分批拷贝过来,同时在拷贝过程中通过触发器把原表上发生的增量修改同步到新表,最后用一次原子性的重命名操作完成新旧表的切换。
pt-online-schema-change的核心工作流程工具启动后做的第一件事是检查原表结构,确认有没有主键或者唯一索引。这是硬性要求,没有主键的表无法使用这个工具。接着它会根据ALTER语句中的变更内容,生成一张新的临时表,表名通常以_开头加上原表名_new结尾。然后工具在原表上创建三个触发器,分别对应INSERT、UPDATE和DELETE操作,确保在数据拷贝期间原表发生的所有变更都能同步到新表。数据拷贝是分批进行的,工具会把原表的数据按主键顺序切成一个个小块,每次只拷贝一小批数据到新表,拷贝完一批就提交一次事务。这样做的好处是避免长事务占用锁资源,也防止了从库出现严重的复制延迟。所有数据拷贝完成后,工具会执行一次最终的表重命名操作,把原表改成_old结尾的备份表,把新表改成正式表名,整个过程对应用层几乎无感知。
安装和基础用法Percona Toolkit的安装很简单,CentOS或者RHEL系统可以直接用yum安装Percona的官方源,Ubuntu和Debian用apt就行。装好之后pt-online-schema-change命令就可以直接用了。最基础的用法长这样:
pt-online-schema-change --alter "ADD COLUMN age INT DEFAULT 0" D=test,t=users --execute
这条命令的意思是在test库的users表上新增一个age字段,默认值为0。--execute参数表示真正执行变更,不加这个参数的话工具只会做一次模拟运行,把检查结果和执行计划打印出来,不会真正动手。生产环境强烈建议先不加--execute跑一遍,确认所有检查项都通过了,再正式执行。
关键参数详解--max-load参数用来设置系统负载阈值,可以指定Threads_running或者Threads_connected等指标。比如--max-load "Threads_running=50"的意思是,如果当前正在运行的线程数超过50,工具就会暂停数据拷贝,等负载降下来再继续。这个机制能有效防止改表操作拖垮整个数据库。
--critical-load是更严厉的保护机制,一旦触发工具会直接退出,不再继续执行。通常设置得比--max-load更高一些,比如--critical-load "Threads_running=100"。
--chunk-size控制每次拷贝的数据行数,默认是1000行。表特别大的话可以适当调大,比如2000或者3000,但不要设得太大,否则单次事务时间过长会影响主从复制。表比较小或者服务器性能一般的话,调小到500反而更稳妥。
--chunk-time是另一种控制拷贝节奏的方式,指定每次拷贝操作的时间上限,默认0.5秒。工具会根据这个时间自动调整每次拷贝的行数,比固定chunk-size更灵活。
--sleep参数让每批拷贝之间暂停一小段时间,给数据库喘口气的机会。比如--sleep 0.1就是在每批拷贝后休眠0.1秒。这个值设得太大会让改表时间变长,设得太小又起不到降压效果,需要根据实际情况权衡。
--alter-foreign-keys-method用来处理外键约束,可选值有auto、rebuild_constraints、drop_swap和none。外键是pt-online-schema-change的一个痛点,处理起来比较复杂,建议尽量避免在带外键的表上使用这个工具。如果实在避不开,用auto模式让工具自动选择处理方式,但一定要先在测试环境验证。
主从复制环境下的注意事项在配置了主从复制的MySQL架构里,使用pt-online-schema-change需要额外留心几个点。工具默认会检查从库的延迟情况,通过--max-lag参数控制允许的最大延迟秒数,默认是1秒。如果从库延迟超过这个值,工具会自动暂停拷贝等待从库追上。--check-slave-lag参数需要指定从库的地址,格式是h=从库IP,P=端口号。如果有多台从库,可以重复使用这个参数指定多个。另外--recursion-method参数用来指定工具如何发现从库,dsn方式需要提前在某个库里建好DSN表,processlist方式则通过SHOW PROCESSLIST来发现从库,hosts方式则依赖从库在配置文件里指定report_host。大多数场景用processlist就够用了。
处理超大表的实战经验面对上亿行甚至更大的表,光靠默认参数往往不够。首先建议把--chunk-size调整到2000到5000之间,同时配合--chunk-time让工具自动微调。--sleep设成0.05到0.1之间,给数据库留出处理正常业务请求的时间窗口。--max-load和--critical-load一定要设置,这是防止线上事故的最后一道防线。另外有个容易被忽略的参数--statistics,开启后工具会在执行过程中实时打印进度信息,包括已拷贝行数、剩余时间预估、当前拷贝速度等,对监控执行进度很有帮助。
磁盘空间也是个需要提前考虑的问题。工具执行期间会同时存在原表和新表,相当于数据量翻倍,再加上_old备份表,磁盘占用会达到原表的三倍左右。执行前务必确认磁盘剩余空间足够。如果磁盘紧张,可以在执行完成后及时删除_old表,或者在确认变更成功后用--no-drop-old-table参数保留旧表,等业务验证没问题了再手动清理。
常见报错和排查思路遇到"主键或唯一索引不存在"的报错,说明表结构不满足工具要求。解决办法是先给表加上主键,或者用--alter参数配合工具先完成加主键的操作,但这本身就比较矛盾,因为加主键也是需要在线改表的。这种情况可以考虑用pt-osc先加一个自增主键列,再执行其他变更。
"触发器已存在"的报错通常是因为上次执行异常中断,残留了触发器没有清理。需要手动到数据库里DROP掉对应的触发器,名称一般是pt_osc_开头。执行前可以用SHOW TRIGGERS命令确认一下。
"等待获取元数据锁超时"这个报错比较头疼,说明有其他长事务或者未提交的事务在占用表的元数据锁。需要先找出这些阻塞源,KILL掉相关连接后再重试。--lock-wait-timeout参数可以调整等待锁的超时时间,默认是1秒,但调大这个值治标不治本,关键还是要找到阻塞源头。
从库延迟持续增大导致工具反复暂停,这种情况需要检查从库的硬件配置是否跟主库差距太大,或者从库上是否有其他消耗资源的操作。适当放宽--max-lag参数可以缓解,但根本解决办法还是提升从库性能或者优化复制配置。
和其他在线改表方案的对比MySQL 5.6及之后的版本引入了Online DDL功能,直接在ALTER TABLE语句后面加ALGORITHM=INPLACE和LOCK=NONE就能实现在线变更。但Online DDL的限制比较多,比如某些操作仍然会锁表,而且在执行期间会产生大量的日志写入,对性能影响不小。pt-online-schema-change的优势在于更灵活,可以精确控制拷贝节奏,对业务的影响更可控。缺点是操作步骤多,需要触发器配合,而且整个过程耗时通常比Online DDL更长。
另一个常见方案是gh-ost,GitHub开源的在线改表工具。它不用触发器,而是通过解析binlog来捕获增量数据,架构上更优雅,对数据库的侵入性更小。gh-ost还支持暂停、断点续传等高级功能。不过gh-ost依赖binlog格式必须是ROW,而且部署和配置比pt-online-schema-change稍微复杂一些。两个工具各有优劣,pt-online-schema-change胜在成熟稳定、文档丰富、社区支持久,gh-ost则在架构设计上更先进。实际选型时可以根据团队的技术栈和具体需求来决定。
执行前的检查清单正式执行改表操作之前,有几件事必须确认。第一,确认表有主键或者唯一索引。第二,确认数据库磁盘空间足够,至少是原表大小的两倍以上。第三,确认没有未提交的长事务,可以用SHOW PROCESSLIST或者information_schema.innodb_trx来检查。第四,确认从库状态正常,延迟在可接受范围内。第五,先在测试环境完整跑一遍,验证ALTER语句的正确性和工具参数的合理性。第六,确认触发器和_old表的清理策略,避免残留对象影响后续操作。第七,选择业务低峰期执行,虽然工具号称不锁表,但表重命名切换的瞬间还是会有短暂的锁操作,而且数据拷贝本身也会消耗系统资源。
执行过程中的监控要点工具跑起来之后不能放任不管,需要持续关注几个指标。数据库的Threads_running和Threads_connected是否在正常范围内,如果持续偏高说明--max-load参数可能需要调整。从库延迟是否在增大,如果延迟持续累积,工具会自动暂停,但暂停时间过长也会影响业务。磁盘空间是否在预期范围内消耗,如果增长过快需要提前准备扩容。工具本身的输出日志也很重要,里面会打印当前拷贝的进度和预估剩余时间,还有没有触发任何警告或错误。建议开一个终端持续观察工具的输出,同时另一个终端连上数据库监控系统负载。
特殊场景的处理技巧有些表虽然没有显式定义主键,但存在一个NOT NULL的唯一索引列,这种情况下pt-online-schema-change也能正常工作,它会自动选择这个唯一索引作为数据拷贝的依据。如果表上同时存在多个唯一索引,工具会选择第一个找到的,但可以通过--alter参数配合指定。
分区表的在线改表需要特别注意,pt-online-schema-change对分区表的支持不如普通表完善。如果分区键也在变更范围内,工具可能无法正确处理。建议对分区表进行改表操作前,先在测试环境充分验证,或者考虑用分区交换的方式来完成变更。
表中包含JSON列或者空间数据类型时,触发器可能会遇到数据类型转换的问题。MySQL对JSON类型的支持在触发器层面有一些限制,如果遇到报错,需要检查具体的错误信息,可能需要调整ALTER语句或者考虑其他改表方案。
改表过程中如果业务写入量特别大,触发器的开销可能会成为瓶颈。每个INSERT、UPDATE、DELETE操作都会额外触发一次对新表的写入,相当于写入量翻倍。如果原表的写入TPS已经很高,加上触发器后数据库的写入压力会显著增加。这种情况下建议把--chunk-size调小,--sleep调大,给正常业务写入留出更多资源。
