MySQL 8.0 给大表加索引把从库延迟拉到 30 分钟:online DDL 的 row log 上限与单线程回放

举报
yd_271851511 发表于 2026/11/08 10:45:35 2026/11/08
【摘要】 ALGORITHM=INPLACE 不等于「不影响从库」。主库的 online DDL 会在从库以单线程回放,且 binlog 里是一条 DDL 事件,从库执行期间整个 SQL 线程被占住;再加上 innodb_online_alter_log_max_size 撑爆会直接回滚重来,延迟会成倍放大。这篇给出量化方法和三种规避方案。

## 背景


一张 1.2 亿行的订单表要加一个联合索引(`user_id, created_at`)。业务低峰期在主库执行:


```sql

ALTER TABLE t_order ADD INDEX idx_user_created (user_id, created_at), ALGORITHM=INPLACE, LOCK=NONE;

```


主库本身 6 分钟跑完,业务无感知。但从库延迟从 0 一路涨到 1800 秒,40 分钟后才追平。


## 一、为什么从库比主库慢这么多


三个原因叠加:


**1)DDL 在从库是单线程执行的**


MySQL 8.0 的并行复制(`slave_parallel_workers` / `replica_parallel_workers`)对 DDL 事件**不并行**:遇到 DDL 时必须等所有 worker 把前面的事务回放完,然后由 SQL 线程自己执行这条 DDL,执行期间其它事务全部排队。所以主库 6 分钟的 DDL,从库至少要 6 分钟,且这 6 分钟里所有并发写入都在积压。


**2)主库执行期间产生的 DML 会积累在 row log 里,从库还要再消化一遍**


`ALGORITHM=INPLACE` 期间,主库对这张表的 DML 会写进 `innodb_online_alter_log_max_size`(默认 128MB)限制的临时日志。DDL 结束前要把这些日志合并回新索引。这 6 分钟内的写入量决定了合并耗时,也决定了从库要额外回放多少 binlog。


**3)row log 撑爆 = 全盘重来**


如果这 6 分钟内这张表的 DML 超过 128MB,DDL 直接失败:


```

ERROR 1799 (HY000): Creating index 'idx_user_created' requires rebuild of table

```


失败会回滚,已经做的扫描全部白费。如果是在从库上重试,延迟会再翻一倍。


## 二、先量化,再动手


动手前必须知道两件事:


```sql

-- 1) 这张表在低峰期的写入速率(决定 row log 会不会爆)

SELECT SUM(rows_affected) FROM ... -- 或者直接看 binlog 位点增速

SHOW BINARY LOGS; -- 间隔 60s 看两次,算 B/s


-- 2) 从库当前延迟与并行复制配置

SHOW SLAVE STATUS\G -- Seconds_Behind_Master / Slave_SQL_Running_State

SHOW VARIABLES LIKE 'replica_parallel_workers';

SHOW VARIABLES LIKE 'replica_parallel_type'; -- LOGICAL_CLOCK 才能真并行

```


经验值:`row log 用量 ≈ 写入速率 × DDL 耗时`。我们这张表 DDL 6 分钟、峰值写入 2MB/s,理论用量 720MB,远超 128MB —— 之所以没失败,是因为低峰期写入降到了 0.2MB/s。这就解释了为什么「白天试一次会失败,凌晨跑就过了」。


## 三、三种规避方案


**方案 A:gh-ost / pt-online-schema-change(推荐给超大表)**


- 创建影子表 + 触发器(pt-osc)或 binlog 监听(gh-ost)同步增量,分批 copy 数据;

- 关键参数:`--max-load`、`--critical-load`、`--chunk-time`,把压力摊到几小时;

- 代价:需要额外磁盘(一份表大小),RR 环境要留够空间;gh-ost 需要 binlog_format=ROW。


**方案 B:主库直接跑,但从库先停 SQL 线程再补**


- 在从库 `STOP SLAVE SQL_THREAD`,等主库 DDL 完成后再 `START SLAVE`;

- 好处是避免从库在 DDL 执行期间被长事务占住、把延迟放大;

- 风险是这段时间从库完全不可用于读,且 relay log 会堆积,要确认 `relay_log_space_limit` 和磁盘空间。


**方案 C:拆成两次 DDL**


- 先加单列索引(`user_id`),再扩展成联合索引。两次 DDL 的 row log 用量都减半,通常能把 128MB 撑爆的概率降下来;

- 8.0 支持 `ALTER TABLE ... ADD INDEX` 的 instant 算法只对「末尾追加列」生效,加索引不行。


## 四、事后验证


- `SHOW SLAVE STATUS` 的 `Seconds_Behind_Master` 归零不代表数据一致,它只是个估算值;用 `pt-heartbeat` 或者自己在主库写时间戳表更准;

- 检查索引是否真的 online 建成:`SELECT * FROM information_schema.innodb_metrics WHERE name LIKE '%online%'`;

- 确认 `innodb_online_alter_log_max_size` 的值,128MB 对 1 亿行的表明显偏小,可以临时调到 1GB(需要重启或动态设置,8.0 支持动态)。


## 小结


online DDL 的「online」只保证不锁表、不阻塞读写,**不保证从库不受影响**。真正决定影响大小的是:DDL 耗时 × 期间的写入速率(row log)、从库单线程回放、以及并行复制对 DDL 的天然短板。超大表优先用 gh-ost,中等表至少要把 row log 上限和从库延迟提前算清楚。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。