1. 索引修改的典型场景与核心挑战
在MySQL数据库运维中,索引调整是最常见的高频操作之一。当表数据量达到百万级时,直接执行ALTER TABLE添加或删除索引可能导致长时间锁表,进而引发业务查询阻塞。去年我们一个电商平台的促销活动就曾因索引变更操作不当,导致订单提交接口瘫痪近20分钟。
2. 线上索引修改的四种解决方案对比
2.1 原生ALTER TABLE的锁表现象
直接使用ALTER TABLE语句修改索引时,MySQL会获取元数据锁(MDL)和表锁。以修改user表的name字段索引为例:
sql复制ALTER TABLE user ADD INDEX idx_name (name);
这个操作会对整张表加锁,期间所有读写操作都会被阻塞。在8核32G的服务器上,对500万数据的表执行该操作平均耗时47秒。
2.2 在线DDL工具pt-online-schema-change
Percona开发的pt-online-schema-change是当前最成熟的方案。其核心原理是:
- 创建临时影子表
- 在原表上创建触发器同步数据变更
- 分批拷贝数据到新表
- 原子化切换表名
典型操作命令:
bash复制pt-online-schema-change \
--alter "ADD INDEX idx_name (name)" \
D=database,t=user \
--execute
重要提示:使用前需确保表有主键,且磁盘空间足够存储两份完整数据
2.3 GitHub开源的gh-ost方案
gh-ost采用binlog流式同步机制,相比pt-osc有以下优势:
- 不依赖触发器,减少主库负载
- 支持暂停/恢复操作
- 提供丰富的监控指标
基本使用示例:
bash复制gh-ost \
--alter="ADD INDEX idx_name (name)" \
--database="database" \
--table="user" \
--approve-renamed-columns \
--execute
2.4 MySQL 8.0的即时DDL特性
MySQL 8.0对部分索引操作实现了即时修改(Instant):
- 添加非唯一二级索引(需要排序的索引除外)
- 删除任何类型的索引
这类操作耗时通常在1秒内完成:
sql复制-- 8.0中可即时完成的索引操作
ALTER TABLE user ADD INDEX idx_name (name), ALGORITHM=INSTANT;
3. 生产环境操作全流程指南
3.1 变更前检查清单
-
确认表结构特征:
sql复制SHOW CREATE TABLE user; SELECT COUNT(*) FROM user; -
检查现有索引情况:
sql复制SHOW INDEX FROM user; -
评估数据特征:
sql复制SELECT MIN(LENGTH(name)) as min_len, MAX(LENGTH(name)) as max_len, COUNT(DISTINCT name) as distinct_count FROM user;
3.2 使用pt-osc的具体实施步骤
-
在测试环境验证:
bash复制pt-online-schema-change \ --alter "ADD INDEX idx_name (name)" \ D=database,t=user \ --dry-run -
正式执行时监控:
bash复制watch -n1 "mysql -e 'SHOW PROCESSLIST' | grep -i alter" -
完成后的验证:
sql复制EXPLAIN SELECT * FROM user WHERE name LIKE '张%';
3.3 灰度发布策略
对于大型表建议分阶段实施:
- 先在从库执行变更
- 观察主从延迟情况
- 业务低峰期在主库执行
- 开启performance_schema监控锁等待
4. 性能影响与监控方案
4.1 资源消耗基准测试
在AWS r5.2xlarge实例上测试结果:
| 方案 | CPU峰值 | 内存增量 | 耗时(500万行) | 锁等待时间 |
|---|---|---|---|---|
| 原生ALTER | 85% | 1.2GB | 47s | 47s |
| pt-osc | 35% | 2.8GB | 6分12秒 | <1s |
| gh-ost | 28% | 3.1GB | 5分47秒 | <1s |
| 8.0 INSTANT | 5% | 200MB | 0.8s | 0.8s |
4.2 实时监控指标
-
进程监控:
sql复制SELECT * FROM sys.session WHERE command = 'Query' AND time > 60; -
锁等待监控:
sql复制SELECT * FROM performance_schema.events_waits_current WHERE EVENT_NAME LIKE '%metadata%'; -
复制延迟监控:
sql复制SHOW SLAVE STATUS\G
5. 经典故障案例与解决方案
5.1 案例一:空间不足导致中断
现象:pt-osc执行到80%时因磁盘满失败
解决方案:
- 清理临时表:
sql复制DROP TABLE _user_new; - 扩展磁盘空间后重新执行
- 使用--chunk-size减小批次量
5.2 案例二:唯一键冲突
当添加唯一索引遇到重复数据时:
- 先检查重复数据:
sql复制SELECT name, COUNT(*) FROM user GROUP BY name HAVING COUNT(*) > 1; - 使用IGNORE选项跳过错误:
sql复制ALTER IGNORE TABLE user ADD UNIQUE INDEX idx_name (name);
5.3 案例三:长事务阻塞
遇到"Waiting for table metadata lock"时:
- 查找阻塞源:
sql复制SELECT * FROM sys.innodb_lock_waits; - 终止阻塞会话:
sql复制
KILL [process_id];
6. 高级优化技巧
6.1 大表分批次策略
对于10亿级表建议:
- 按主键范围分批:
bash复制pt-online-schema-change \ --where="id BETWEEN 1 AND 1000000" \ --alter "ADD INDEX idx_name (name)" \ --execute - 配合--chunk-size和--chunk-time调节
6.2 外键表的特殊处理
存在外键约束时需要:
- 添加--alter-foreign-keys-method参数
- 推荐使用rebuild_constraints方法
6.3 云数据库的特殊考量
AWS RDS等托管服务注意:
- 检查可用存储空间(包括临时空间)
- 监控IOPS突发余额
- 考虑使用只读副本先行测试
7. 未来演进方向
InnoDB引擎正在推进更多即时DDL特性:
- 预计MySQL 9.0将支持主键的即时修改
- 空间索引的在线创建已在Roadmap中
- 官方正在开发无触发器变更方案
当前过渡期建议:
- 新项目优先使用MySQL 8.0+
- 存量系统配合pt-osc或gh-ost
- 建立变更评估机制,记录每次索引修改的耗时影响