问题

什么是OnlineDDL?

答案

核心概念

Online DDL是MySQL 5.6引入的特性,允许在执行DDL操作(如加索引、修改列)时不阻塞DML操作(INSERT、UPDATE、DELETE),实现表结构变更期间业务的正常读写。

传统DDL的问题

MySQL 5.6之前:

ALTER TABLE users ADD INDEX idx_age(age);

执行过程:

  1. 创建临时表,按新结构定义
  2. 将原表数据逐行复制到临时表
  3. 复制期间锁表,禁止DML操作
  4. 替换原表

问题:

  • 长时间锁表:大表添加索引可能耗时数小时
  • 业务中断:锁表期间无法写入,导致业务不可用
  • 双倍存储:临时表占用额外磁盘空间

Online DDL的改进

核心思想: 通过增量日志记录DDL期间的DML操作,最后合并应用,避免长时间锁表。

执行流程:

1. 加MDL读锁(Metadata Lock)
   ↓
2. 升级为MDL写锁(短暂)
   ↓
3. 降级为MDL读锁
   ↓
4. 执行DDL操作(如构建索引)
   同时记录DML操作到 row log
   ↓
5. 升级为MDL写锁(短暂)
   ↓
6. 应用row log中的增量DML
   ↓
7. 释放MDL锁,完成DDL

关键点:

  • 大部分时间持有MDL读锁,允许DML并发执行
  • 仅在开始和结束时短暂持有MDL写锁,阻塞DML
  • 使用row log记录DDL期间的数据变更

Online DDL的算法

MySQL支持三种DDL算法:

1. COPY算法(传统方式)

ALTER TABLE users ADD COLUMN status INT,
ALGORITHM=COPY;
  • 创建临时表,复制数据
  • 全程锁表,不允许DML
  • 适用于所有DDL操作

2. INPLACE算法(Online DDL)

ALTER TABLE users ADD INDEX idx_age(age),
ALGORITHM=INPLACE, LOCK=NONE;
  • 不创建临时表,原地修改
  • 允许并发DML(取决于LOCK级别)
  • 适用于大多数索引操作

3. INSTANT算法(MySQL 8.0+)

ALTER TABLE users ADD COLUMN status INT DEFAULT 0,
ALGORITHM=INSTANT;
  • 仅修改元数据,不改数据文件
  • 秒级完成,无论表多大
  • 仅适用于特定操作(如末尾加列、修改默认值)

LOCK级别控制

通过LOCK子句控制DDL期间的并发行为:

-- 1. LOCK=NONE:允许并发读写(最优)
ALTER TABLE users ADD INDEX idx_age(age), LOCK=NONE;

-- 2. LOCK=SHARED:允许并发读,禁止写
ALTER TABLE users MODIFY COLUMN name VARCHAR(100), LOCK=SHARED;

-- 3. LOCK=EXCLUSIVE:禁止并发读写(等同于传统DDL)
ALTER TABLE users ADD COLUMN status INT, LOCK=EXCLUSIVE;

-- 4. LOCK=DEFAULT:由MySQL自动选择最宽松的锁级别
ALTER TABLE users ADD INDEX idx_email(email), LOCK=DEFAULT;

常见DDL操作分类

DDL操作 算法支持 锁级别 是否重建表
添加索引 INPLACE NONE
删除索引 INPLACE NONE
添加列(末尾) INSTANT NONE 否(8.0)
删除列 INPLACE EXCLUSIVE
修改列类型 COPY EXCLUSIVE
修改列默认值 INSTANT NONE
添加主键 INPLACE EXCLUSIVE
删除主键 COPY EXCLUSIVE
修改字符集 COPY EXCLUSIVE
OPTIMIZE TABLE INPLACE NONE

示例说明

示例1:添加索引(INPLACE + NONE)

-- 表有1000万行数据
ALTER TABLE orders ADD INDEX idx_user_id(user_id),
ALGORITHM=INPLACE, LOCK=NONE;

-- 执行过程:
-- 1. 扫描主键索引,构建新索引(可能耗时数分钟)
-- 2. 同时允许INSERT、UPDATE、DELETE操作
-- 3. row log记录增量变更
-- 4. 最后合并row log,短暂阻塞(毫秒级)

示例2:添加列(INSTANT)

-- MySQL 8.0+
ALTER TABLE users ADD COLUMN last_login DATETIME DEFAULT NULL,
ALGORITHM=INSTANT;

-- 执行过程:
-- 1. 仅修改表的元数据(.frm文件)
-- 2. 不修改数据文件(.ibd)
-- 3. 秒级完成,业务无感知

示例3:修改列类型(COPY)

-- 扩大VARCHAR长度
ALTER TABLE users MODIFY COLUMN name VARCHAR(200),
ALGORITHM=COPY, LOCK=EXCLUSIVE;

-- 执行过程:
-- 1. 创建临时表
-- 2. 复制全表数据(耗时长,锁表)
-- 3. 替换原表

生产环境最佳实践

1. 使用pt-online-schema-change工具

# Percona Toolkit工具,实现更平滑的Online DDL
pt-online-schema-change \
  --alter "ADD INDEX idx_age(age)" \
  --execute \
  D=test,t=users

# 原理:
# 1. 创建影子表(新结构)
# 2. 创建触发器同步数据
# 3. 分批复制数据
# 4. 最后原子切换表

2. 控制执行时间窗口

-- 低峰期执行DDL
ALTER TABLE orders ADD INDEX idx_create_time(create_time),
ALGORITHM=INPLACE, LOCK=NONE;

-- 监控执行进度
SHOW PROCESSLIST;
-- 查看State字段:altering table、committing

3. 设置超时保护

-- 防止MDL写锁等待时间过长
SET SESSION lock_wait_timeout = 5;  -- 5秒超时

ALTER TABLE users ADD INDEX idx_email(email);

4. 分批次执行

-- 不好的做法:一次性多个DDL
ALTER TABLE users
  ADD INDEX idx_age(age),
  ADD INDEX idx_email(email),
  ADD COLUMN status INT;

-- 推荐做法:拆分执行,观察影响
ALTER TABLE users ADD INDEX idx_age(age);
-- 观察业务指标,确认无影响后继续
ALTER TABLE users ADD INDEX idx_email(email);

注意事项

  1. 磁盘空间:INPLACE算法虽不创建临时表,但构建索引仍需额外空间(约原表大小)
  2. 主从延迟:DDL在主库执行完后,从库也要重放,可能加剧延迟
  3. 外键约束:有外键的表DDL可能退化为COPY算法
  4. 版本差异:MySQL 5.6/5.7/8.0的Online DDL能力逐步增强
  5. row log大小:DDL期间DML过多,row log可能占满tmpdir

监控与排查

-- 查看当前DDL进度(MySQL 8.0+)
SELECT * FROM performance_schema.events_stages_current
WHERE EVENT_NAME LIKE 'stage/innodb/alter%';

-- 查看MDL锁等待
SELECT * FROM performance_schema.metadata_locks
WHERE OBJECT_SCHEMA = 'test' AND OBJECT_NAME = 'users';

-- 查看临时文件使用
SHOW VARIABLES LIKE 'innodb_tmpdir';

面试答题总结

Online DDL是MySQL 5.6+提供的不锁表执行DDL的能力,通过INPLACE算法row log增量日志,允许DDL期间并发执行DML操作。核心是仅在开始和结束时短暂持有MDL写锁,大部分时间持有MDL读锁。支持三种算法:COPY(全表复制)、INPLACE(原地修改)、INSTANT(元数据修改)。生产环境建议使用ALGORITHM=INPLACE, LOCK=NONE,配合pt-osc工具,在低峰期执行,监控磁盘空间和主从延迟。