数据库的线上变更(DDL 修改表结构、索引调整、配置参数修改等)是运维中最常见也最容易出事故的环节。一条看似简单的 ALTER TABLE,可能锁表导致服务瘫痪,也可能让原本好好的查询突然走错索引、CPU 飙红。建立一套规范的变更流程和灰度验证机制,不是为了增加工作量,而是为了让每一次操作都安全可控。
24.5.1 变更前的充分准备
任何变更都不能直接在线上随手执行。准备阶段是整个流程的起点,质量直接决定了变更能否成功。
(1)在测试环境验证
正式变更前,必须在开发环境和预发布环境完整执行一遍。不只是测试功能正确性,还要:
- 模拟数据量:测试环境应导入与线上同量级的测试数据,至少要覆盖表大小的近似值。不要用几千行的表去验证几千万行的表变更时长。
- 检查锁与阻塞:使用
SHOW PROCESSLIST或监控工具,确认 DDL 语句在执行时不会产生长时间的锁等待或阻塞业务操作。 - 测试回滚方案:变更后如果出问题,怎么回退?比如新加索引,可以直接删除索引;修改字段类型,可能要依赖备份恢复。必须在测试中演练一次回滚。
(2)预估变更影响
对于可能造成长时间锁定的操作尤其要谨慎,比如:
- 修改大表的字段类型,通常需要全表复制,耗时与表大小成正比。
- 添加全文索引或空间索引,比普通索引更耗资源。
- 在 MySQL 5.7 及之前版本中,许多 DDL 操作会重建表(copy table),执行期间会阻塞写入。
使用 MySQL 8.0 的 ALGORITHM=INSTANT 或 ALGORITHM=INPLACE 特性可以显著降低锁表风险。在变更前,可以用 ALTER TABLE ... , ALGORITHM=INSTANT 来检测是否支持即时变更。如果必须走 copy 模式,可能需要在流量低谷甚至停服窗口执行。预估变更耗时,可以使用第三方工具如 pt-online-schema-change 来模拟执行并观察进度。
(3)准备完整的变更脚本
变更脚本应包括:
- 具体执行的 SQL 语句(加注释说明原因)。
- 必要的备份语句:比如变更前先
CREATE TABLE xxx_bak AS SELECT * FROM xxx(适用于小表),或者确认已有的物理备份可以恢复。 - 验证语句:变更完成后,如何确认表结构已更新(如
SHOW CREATE TABLE)、索引是否生效(SHOW INDEX FROM)、数据是否完整。
所有脚本必须写在工单或文档中,禁止凭记忆直接敲命令。建议用 BEGIN 和 ROLLBACK 包裹在事务中执行(如果引擎支持),以便出错时回滚。
24.5.2 选择适合的变更工具与策略
线上环境应避免直接使用 ALTER TABLE,尤其是大数据量表。主流工具如下:
- pt-online-schema-change(Percona Toolkit):
- 原理:创建一个与原始表结构相同的新表,在新表上执行 DDL 变更,然后将原表的数据以触发器同步到新表,最后在业务低峰进行原子重命名切换。
- 优点:整个过程中不会阻塞读写,对业务无感知。
- 注意:需要主键或唯一索引,且磁盘空间至少是原表的 2 倍。触发器期间会对原表的写入增加少量开销。
- gh-ost(GitHub):
- 原理:不使用触发器,而是模拟从库,通过 Binlog 捕获变更并应用到 ghost 表,切换时同样是原子重命名。
- 优点:对主库负载更低,暂停和限速更灵活,避免触发器影响主库。
- MySQL 8.0 原生 DDL:
- 对于支持 INSTANT 算法(如添加列、重命名索引等)或 INPLACE 算法(如添加普通索引)的操作,可以直接执行,风险较低。但依然建议在从库先做验证。
选择哪个工具?如果表数据量超过百万行,建议优先使用 pt-online-schema-change 或 gh-ost。小表或非核心表,可在从库执行完毕后再主库执行。
24.5.3 灰度发布与分步执行
数据库变更也要像应用代码一样进行灰度发布,遵循“先从库,后主库;先边缘,后核心”的原则。
(1)先在从库上执行
选择一台或多台不承担线上流量的只读从库,先执行变更脚本,观察:
- 执行耗时是否符合预期。
- 执行期间从库延迟是否增大,复制是否中断。
- 执行完成后,检查索引、数据完整性,用业务查询验证性能。
如果从库验证没问题,再继续下一步。如果变更引起从库严重延迟或复制出错,立即停止,调整策略或修复,不要贸然执行主库。
(2)主库灰度
如果架构支持读写分离,可以在主库执行变更前,先将部分读流量切到已经变更成功的从库上,观察业务指标没有问题。然后选择主库流量较低的时段(如凌晨),执行变更。
对于核心大表的 DDL,如果使用 pt-online-schema-change,可以设置 --max-load 和 --critical-load 参数,让工具在检测到系统负载过高时自动暂停,保护主库性能。还可以设置 --execute 只打印而不执行,人工确认无误后再去掉该参数真正执行。
(3)分批次执行多张表
如果有多个表需要变更,不要一次性全部执行。每次选择一张或一组关联表,观察 10-15 分钟,确认监控指标(CPU、IO、慢查询、复制延迟)正常,再执行下一批。这样即使某个变更引发问题,也能迅速定位。
24.5.4 实时监控与应急回滚
变更期间必须全程监控,不能敲完命令就走人。
- 监控核心指标:
- 主库及从库的 CPU 使用率、磁盘 IO 负载、内存使用率。
- 复制延迟(
SHOW SLAVE STATUS中的Seconds_Behind_Master),如果复制延迟持续增加,说明从库压力大或主库产生的写入过于密集。 - 慢查询数量:如果突然增加,可能变更导致执行计划改变。
- 备好应急方案:
- 备份数据:变更前确保有最新的逻辑或物理备份,且恢复流程已经测试。
- 回滚脚本:对于可逆操作(如新增索引),准备删除索引的 SQL;对于不可逆操作(如修改字段名、删除列),需要明确是依赖备份恢复还是执行逆操作。
- 如果使用 pt-osc 或 gh-ost 等工具,工具本身可以通过
--no-swap-tables或直接drop trigger来终止,一般不会导致数据不一致。
一旦发现异常,立即暂停或回滚变更。如果变更导致线上服务大面积报错,优先恢复业务,再定位问题原因。不要因为“脚本已经执行了一半”而犹豫回滚。
24.5.5 变更后的验证与收尾
灰度变更完成后,还需要做最终的验证确认。
- 功能验证:调用关键业务的接口,检查写入和查询是否正常。可以跑一遍预先准备好的回归测试 SQL。
- 性能对比:对比变更前后的执行计划,确保重要的 SQL 没有因为索引变更而退化。使用
EXPLAIN检查关键查询是否仍然使用了预期的索引。 - 监控观察:变更后至少持续观察 30 分钟,确认 CPU、慢查询等指标没有恶化。
- 清理工作:如果使用了 pt-osc 或 gh-ost,工具会自动删除临时表,但要确认冗余对象确实删除。如果新增索引,注意观察索引使用情况,如有冗余就在稳定运行一段时间后做清理。
最后,把本次变更的执行情况、耗时、遇到什么问题、如何解决的,记录到变更日志或知识库中,方便后续复盘和团队参考。
线上变更不是冒险,而是一个可控的工程流程。尊重流程,就是在保护你和用户的数据安全。