1. 索引创建的传统认知误区
很多MySQL开发者都经历过这样的场景:当我们需要给一个大表添加索引时,总会下意识地选择在业务低峰期操作,因为"常识"告诉我们添加索引会导致表锁,进而阻塞所有读写操作。但实际情况真的如此吗?
我最近在生产环境处理一个2TB的用户行为表时,意外发现这个"常识"并不完全正确。通过深入测试和源码分析,发现从MySQL 5.6开始引入的Online DDL机制,已经让索引创建过程发生了质的变化。下面分享我的实测经验和原理分析。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Online DDL工作机制解析
2.1 什么是Online DDL
Online DDL是MySQL 5.6引入的重要特性,允许在不锁表的情况下执行DDL操作。其核心原理是通过以下三个阶段实现:
- 初始化阶段:短暂获取元数据锁,创建临时表结构
- 执行阶段:在临时表上执行DDL变更,同时记录原表的DML操作日志
- 提交阶段:应用日志变更,切换新旧表定义
整个过程最耗时的索引构建操作在第二阶段完成,此时原表仍然可以正常读写。
2.2 支持Online DDL的索引操作
并非所有索引操作都支持Online模式,具体支持情况如下:
| 操作类型 | Online支持 | 锁级别 |
|---|---|---|
| ADD INDEX | 支持 | 共享元数据锁 |
| ADD FULLTEXT INDEX | 支持 | 共享元数据锁 |
| ADD SPATIAL INDEX | 支持 | 共享元数据锁 |
| DROP INDEX | 支持 | 独占元数据锁 |
| RENAME INDEX | 不支持 | 表锁 |
注意:即使支持Online DDL,在开始和结束阶段仍会短暂获取元数据锁(MDL),通常持续毫秒级
3. 实测对比:传统DDL vs Online DDL
3.1 测试环境配置
我在测试环境准备了以下配置:
- MySQL 8.0.28
- 测试表:500万行数据,约1.2GB
- 存储引擎:InnoDB
- 隔离级别:REPEATABLE READ
3.2 传统DDL执行情况
使用ALGORITHM=COPY方式(模拟旧版本行为):
sql复制ALTER TABLE user_behavior
ADD INDEX idx_action_time (action_time) ALGORITHM=COPY, LOCK=SHARED;
监控显示:
- 执行时间:32秒
- 锁持续时间:整个DDL过程
- 影响:所有写操作被阻塞
3.3 Online DDL执行情况
使用默认参数(自动选择ALGORITHM=INPLACE):
sql复制ALTER TABLE user_behavior
ADD INDEX idx_action_time (action_time);
监控显示:
- 执行时间:28秒
- 锁持续时间:开始和结束各约50ms
- 影响:仅短暂阻塞DDL开始时的并发事务
4. 实现原理深度剖析
4.1 InnoDB的索引构建过程
Online DDL创建二级索引的关键步骤:
- 扫描聚簇索引,提取索引列和主键值
- 在内存中构建排序键值对
- 将排序后的记录插入到新的B+树索引中
- 应用DDL执行期间累积的增量变更
整个过程不需要重建表数据,因此可以避免长时间的锁表。
4.2 增量变更日志应用机制
MySQL通过以下方式保证数据一致性:
- 在DDL执行期间,所有DML操作会被记录到临时日志(alter log)
- 日志内容包含:操作类型(INSERT/UPDATE/DELETE)、行数据、索引变更
- DDL完成后,将这些变更应用到新索引上
5. 生产环境最佳实践
5.1 参数优化建议
对于大型表索引创建,建议调整以下参数:
sql复制SET SESSION innodb_online_alter_log_max_size=256MB;
SET SESSION innodb_sort_buffer_size=64MB;
参数说明:
innodb_online_alter_log_max_size:增大可减少日志空间不足导致的DDL失败innodb_sort_buffer_size:提升排序效率
5.2 监控与问题排查
关键监控指标:
sql复制SELECT * FROM performance_schema.events_statements_current
WHERE SQL_TEXT LIKE '%ALTER TABLE%';
SHOW PROCESSLIST;
常见问题处理:
- 空间不足:确保有足够的临时表空间
- 超时中断:适当增大lock_wait_timeout
- 复制延迟:在从库上设置slave_type_conversions=ALL_NON_LOSSY
6. 特殊场景注意事项
6.1 全文索引的特殊性
创建FULLTEXT索引时:
- 仍然使用Online DDL机制
- 但构建倒排索引的过程CPU消耗较高
- 建议在低峰期操作
6.2 主键索引的差异
修改主键的操作:
- 不支持Online DDL
- 需要重建整个表
- 必须使用ALGORITHM=COPY
6.3 外键约束的影响
当表存在外键约束时:
- 添加索引通常不受影响
- 但删除被引用的索引会触发表锁
- 建议先暂时禁用外键检查:
sql复制SET FOREIGN_KEY_CHECKS=0; -- 执行DDL SET FOREIGN_KEY_CHECKS=1;
7. 版本兼容性指南
不同MySQL版本的Online DDL支持情况:
| 版本 | 支持程度 | 重要改进 |
|---|---|---|
| 5.5及之前 | 不支持 | - |
| 5.6 | 基本支持 | 引入ALGORITHM和LOCK语法 |
| 5.7 | 增强支持 | 优化日志应用性能 |
| 8.0 | 完整支持 | 支持原子DDL、并行构建索引 |
提示:使用低于5.6的版本时,可以考虑使用pt-online-schema-change工具实现类似效果
8. 性能对比实测数据
在相同硬件环境下,对不同规模表的测试结果:
| 数据量 | Online DDL时间 | COPY方式时间 | 写入影响时间 |
|---|---|---|---|
| 100万行 | 5.2秒 | 6.8秒 | 0.1秒 vs 6.8秒 |
| 500万行 | 28秒 | 32秒 | 0.1秒 vs 32秒 |
| 1000万行 | 1分12秒 | 1分45秒 | 0.2秒 vs 1分45秒 |
| 5000万行 | 8分33秒 | 12分47秒 | 0.3秒 vs 12分47秒 |
从数据可以看出,随着数据量增大,Online DDL的优势更加明显。
9. 常见问题解决方案
9.1 如何强制使用Online DDL
明确指定算法和锁类型:
sql复制ALTER TABLE table_name
ADD INDEX index_name (column_name)
ALGORITHM=INPLACE, LOCK=NONE;
9.2 进度监控技巧
通过performance_schema监控进度:
sql复制SELECT * FROM performance_schema.events_stages_current
WHERE EVENT_NAME LIKE '%stage/innodb/alter%';
9.3 大表优化建议
对于特别大的表(超过10GB):
- 先在从库执行DDL,然后主从切换
- 使用gh-ost等第三方工具
- 分批次添加索引(如先添加部分列的索引)
10. 内部实现源码分析
在MySQL源码层面,Online DDL的关键实现位于:
sql/sql_table.cc:处理ALTER TABLE命令storage/innobase/row/row0mysql.cc:实现行操作storage/innobase/row/row0log.cc:处理增量日志
核心函数调用链:
code复制mysql_execute_command()
-> mysql_alter_table()
-> ha_innobase::prepare_inplace_alter_table()
-> row_merge_build_indexes()
-> row_merge_sort()
通过源码可以确认,索引构建过程确实不需要持续持有表锁。
