摘要
在线业务中,数据库表结构变更(DDL)是最常见的运维操作之一,也是最容易出现锁表、阻塞业务的风险点。本文分析传统数据库 DDL 的痛点,对比 MySQL 生态的在线 DDL 工具方案,深入解析 TiDB 的 Online DDL 无锁设计原理与最佳实践。
本文适合谁:需要在不停机、不锁表的前提下进行表结构变更的 DBA、后端工程师和 SRE。
一、传统数据库 DDL 的痛点
在 MySQL 的早期版本中,DDL 操作直接修改表定义文件,会获取 元数据锁(Metadata Lock) 甚至 表级锁,导致:
| DDL 操作 | 影响范围 | 典型阻塞时间 |
|---|---|---|
| `ADD INDEX` | 阻塞写入,读取受限 | 大表可能数小时 |
| `ADD COLUMN` | 全表重建 | 与数据量成正比 |
| `MODIFY COLUMN` | 全表重建 | 与数据量成正比 |
| `DROP COLUMN` | 全表重建 | 与数据量成正比 |
| `CHANGE COLUMN TYPE` | 全表重建 | 与数据量成正比 |
对于千万级、亿级数据量的在线业务表,一次 DDL 操作可能导致业务不可用数小时,这在 7x24 运行的互联网业务中是不可接受的。
1.1 MySQL 8.0 的改进
MySQL 8.0 引入了 Instant DDL,支持 `ADD COLUMN`(末尾列)的秒级完成,但仍然有较多限制:
- 仅支持在表末尾添加列
- 不支持修改列类型、删除列
- 不支持添加索引
- 对 `ROW_FORMAT=COMPRESSED` 表有限制
二、MySQL 在线 DDL 工具方案
在原生 DDL 能力不足的情况下,社区和业界发展出多种在线 DDL 工具:
| 工具 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| gh-ost | 通过 Binlog 回放构建影子表 | 无触发器,不影响主库负载 | 需要 Binlog,配置复杂 |
| pt-online-schema-change | 创建影子表 + 触发器同步 | 成熟稳定,支持多种 DDL | 触发器增加主库负载 |
| MySQL 8.0 Instant DDL | 直接修改元数据 | 真正秒级完成 | 支持的 DDL 类型有限 |
| TiDB Online DDL | 异步后台任务 + Raft DDL | 无锁、原生支持、大表友好 | 集群方案,非单机 |
2.1 gh-ost 工作流程(简述)
1. 创建空影子表(新结构)
2. 创建 Binlog 异步回放通道
3. 增量复制主表变更到影子表
4. 数据迁移完成后,原子切换表名
5. 删除旧表
gh-ost 的核心优势是不使用触发器,避免了对主库写入的额外开销。但需要额外的 Binlog 解析进程,且对回放速度有严格要求。
三、TiDB Online DDL 原理
TiDB 的 DDL 架构与传统数据库有本质区别,采用 异步任务 + Raft DDL 设计,从架构层面避免锁表。
3.1 架构设计
TiDB DDL 架构:
┌──────────────────────────────────────────────────┐
│ TiDB Cluster │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │TiDB-1 │ │TiDB-2 │ │TiDB-3 │ SQL 层 │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └────────────┼────────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ DDL Owner │ ← 单 Owner 模式 │
│ │ (异步执行 DDL) │ │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ PD │ 元数据管理 │
│ └────────┬────────┘ │
│ ▼ │
│ Raft DDL Jobs (元数据变更) │
└──────────────────────────────────────────────────┘
关键设计:
- 单 Owner 模式:集群中只有一个 TiDB 节点作为 DDL Owner,负责执行 DDL 任务,避免多节点并发执行导致冲突
- 异步执行:DDL 语句提交后立即返回(`CREATE INDEX` 返回 Job ID),实际变更在后台异步执行
- Raft DDL:DDL 的元数据变更通过 Raft 协议保证一致性,所有 TiDB 节点最终看到相同的 Schema
3.2 大表加索引流程
以 `ADD INDEX` 为例,TiDB 的执行流程:
1. Client: ALTER TABLE orders ADD INDEX idx_user_id (user_id);
→ 返回: DDL Job Created (Job ID: 123)
2. 后台阶段 1 - Schema 变更(秒级)
→ 元数据写入 TiKV,所有节点可见新索引定义
→ 注意:此时索引数据尚未填充,查询不会使用该索引
3. 后台阶段 2 - 数据回填(异步)
→ 遍历表数据,逐 Region 填充索引数据
→ 写入并发度可控,不影响在线读写
→ 支持暂停、恢复、取消
4. 后台阶段 3 - 索引生效
→ 回填完成后,CBO 优化器自动开始使用该索引
→ 整个过程 DML 操作不受任何阻塞
3.3 TiDB DDL 操作分类
| 操作类型 | 是否阻塞 DML | 执行模式 | 典型耗时 |
|---|---|---|---|
| `ADD INDEX` | 不阻塞 | 异步后台回填 | 与数据量相关 |
| `ADD COLUMN` | 不阻塞(末尾列秒级) | 元数据变更 | < 1 秒 |
| `MODIFY COLUMN` | 不阻塞 | 异步回填 | 与数据量相关 |
| `DROP COLUMN` | 不阻塞 | 元数据变更 + 异步清理 | < 1 秒(标记删除) |
| `DROP INDEX` | 不阻塞 | 元数据变更 | < 1 秒 |
| `RENAME TABLE` | 不阻塞 | 元数据变更 | < 1 秒 |
四、在线 DDL 最佳实践
4.1 大表加索引
-- 推荐:指定加索引的并发度和批次大小
ALTER TABLE orders ADD INDEX idx_user_id (user_id);
-- 查看进度
ADMIN SHOW DDL JOBS;
-- 或查看详细状态
ADMIN SHOW DDL JOB QUERIES 123;
4.2 DDL 操作注意事项
| 注意事项 | 说明 |
|---|---|
| 控制并发 DDL 数量 | 同一时刻建议不超过 10 个活跃 DDL,避免资源争抢 |
| 避免高峰期执行大 DDL | 虽不锁表,但大表回填会消耗 IO 和 CPU |
| 关注 DDL Job 状态 | 使用 `ADMIN SHOW DDL JOBS` 持续监控执行进度 |
| 大表变更使用批处理 | 对于超大表,可结合 `tidb_ddl_reorg_worker_cnt` 控制回填速度 |
4.3 关键参数配置
-- 控制回填并发度(默认 4,建议根据集群规模调整)
SET GLOBAL tidb_ddl_reorg_worker_cnt = 8;
-- 控制回填批次大小(默认 64 行/批)
SET GLOBAL tidb_ddl_reorg_batch_size = 256;
五、TiDB DDL vs MySQL gh-ost 对比
| 维度 | TiDB Online DDL | MySQL + gh-ost |
|---|---|---|
| 锁表风险 | 无(架构层面保证) | 低(但需正确配置) |
| 实现方式 | 原生集群支持 | 外部工具 + Binlog |
| 大表加索引 | 异步回填,不阻塞读写 | 创建新表 + 数据复制 + 切换 |
| 运维复杂度 | 低(单命令) | 高(监控回放延迟、配置参数) |
| 对主库影响 | 回填占用资源但无锁 | 无触发器但需读取 Binlog |
| 失败恢复 | 自动回滚,幂等设计 | 需手动清理影子表 |
FAQ
Q1:TiDB 加索引会锁表吗?
不会。TiDB 的 `ADD INDEX` 采用异步后台回填机制,在整个索引构建过程中,所有的 DML 操作(INSERT、UPDATE、DELETE、SELECT)均不受阻塞。索引回填完成后,CBO 优化器会自动选择使用新索引。
Q2:大表加索引需要多长时间?
取决于数据量和集群资源。以 1 亿行数据的表为例,在中等规模集群(3 TiKV 节点,每节点 16 核 64GB)上,回填一个普通 B-Tree 索引通常需要 10-30 分钟。可通过 `tidb_ddl_reorg_worker_cnt` 调整回填速度。
Q3:TiDB DDL 和 MySQL gh-ost 有什么区别?
核心区别在于架构层面:TiDB Online DDL 是数据库原生能力,通过 Raft 保证元数据一致性,DDL 执行过程中无需外部工具配合。而 gh-ost 是一个独立的进程工具,依赖 MySQL 的 Binlog 进行数据同步,需要额外的部署和运维。TiDB 的方案更简单、更可靠、且支持更多 DDL 操作类型的无锁执行。
Q4:DDL 操作失败怎么办?
TiDB 的 DDL 操作具有 自动回滚机制。如果 DDL Owner 节点在执行过程中宕机,PD 会重新选举新的 DDL Owner,新 Owner 会从上一次的检查点恢复执行。如果需要手动取消,可以执行 `ADMIN CANCEL DDL JOBS`。已提交的 DDL Job 会记录在 `mysql.tidb_ddl_job` 表中,便于审计和恢复。
总结
TiDB 的 Online DDL 通过异步任务 + Raft DDL 的架构设计,从根本上解决了传统数据库 DDL 锁表的问题。无论是加索引、加列还是修改列,TiDB 均可在不阻塞在线业务的前提下完成,极大简化了 DBA 的运维工作,为业务的持续演进提供了基础设施保障。
下一步行动
- 试用 TiDB Online DDL:前往 TiDB Cloud 免费试用,在线体验无锁 DDL
- 获取 DDL 最佳实践:下载 TiDB 运维手册
- 参加线上研讨会:TiDB DDL 实战分享