0
0
1
0
博客/.../

什么是数据库 Schema 变更在线执行?TiDB DDL 无锁设计解析

 Billmay表妹  发表于  2026-06-02
原创

摘要

在线业务中,数据库表结构变更(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 的运维工作,为业务的持续演进提供了基础设施保障。


下一步行动

相关资源

0
0
1
0

版权声明:本文为 TiDB 社区用户原创文章,遵循 CC BY-NC-SA 4.0 版权协议,转载请附上原文出处链接和本声明。

评论
暂无评论