pt-online-schema-change 遇上 Galera:WHERE 条件为何会放大 Chunk 并触发流控

2026-09-28 27 预计阅读时间: 1 分钟
来源: percona.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

pt-online-schema-change(pt-osc)依靠分块复制和动态调节 chunk 大小,在大表上执行在线 DDL。通常,这套机制可以把单次复制控制在较短时间内;但当 --where 过滤出稀疏且分布不均的数据时,自动扩容可能错误地认为系统还有大量余量,最终生成过大的事务。在 Galera 或 Percona XtraDB Cluster 中,这种突发写入又可能推高接收队列,触发全局 Flow Control。

自动调节为什么会被稀疏条件误导

pt-osc 会沿选定索引切分数据,并根据前一个 chunk 的执行时间调整后续 chunk,目标是让每批复制接近 --chunk-time。可以把它简化理解为:

下一批行数 ≈ 当前批行数 × 目标时间 / 当前耗时

这不是工具内部算法的精确公式,但足以说明风险。如果 --where 在一段主键范围内只能匹配少量记录,该 chunk 会很快完成,工具因而扩大下一批的扫描范围或预计行数。

问题出现在数据分布突然变密时。例如,表按 id 分块,但条件只选择某个租户:

WHERE tenant_id = 9001

若该租户在早期主键区间几乎没有数据,而在后面的区间非常密集,前面的快速 chunk 会推动自动扩容。进入密集区后,一个已经变大的 chunk 可能一次复制大量记录,形成大事务和大 writeset。

因此,平均选择率并不够。真正需要观察的是:

  • 条件命中的记录是否沿 chunk 索引均匀分布;
  • 稀疏区和密集区之间是否存在明显边界;
  • --where 使用的列是否能与 chunk 索引协同过滤;
  • 单个放大后的 chunk 会产生多大的事务和复制流量。

Galera Flow Control 为什么会把问题扩散到整个集群

Galera 节点需要接收并应用其他节点产生的 writeset。当某个节点的接收队列增长过快时,它会发送 Flow Control 请求,让集群暂缓提交新事务,给落后节点追赶的时间。

大 chunk 带来的影响不是简单的“这一批多跑几秒”:

  1. pt-osc 在影子表上批量插入数据;
  2. 该事务提交后形成较大的复制负载;
  3. 较慢节点的接收或应用队列开始堆积;
  4. 节点发出 Flow Control;
  5. 集群中的业务写入也随之暂停或延迟。

这解释了为什么单节点上的复制查询看起来还能接受,应用端却出现周期性延迟尖峰。瓶颈可能不在执行 pt-osc 的节点,而在磁盘较慢、正在备份或承担额外查询的其他节点。

不要把调高 Galera 队列阈值当作第一反应。更大的队列会消耗更多内存,也可能只是延迟 Flow Control,而没有消除过大的 chunk。

--where 不只是性能选项

必须特别注意:--where 会限制复制到新表中的行。切换影子表后,不满足条件的旧数据不会自动保留。因此,它通常适用于明确的数据保留、归档或裁剪场景,而不是单纯为了“减少本次 DDL 的压力”。

执行前至少确认:

SELECT COUNT(*) AS total_rows
FROM orders;

SELECT COUNT(*) AS rows_to_keep
FROM orders
WHERE tenant_id = 9001
  AND created_at >= '2025-01-01';

如果你的目标是给整张表增加列或索引,就不应为了提速而随意添加 --where。否则,一次看似正常的在线 DDL 可能变成数据裁剪操作。

一套更保守、可改造的执行方式

下面假设确实只需要保留满足条件的数据,并且表以 id 为主键。请先替换主机、数据库、表名和业务阈值。

先检查索引与执行计划:

SHOW INDEX FROM app.orders;

EXPLAIN
SELECT id
FROM app.orders FORCE INDEX (PRIMARY)
WHERE id >= 1
  AND tenant_id = 9001
  AND created_at >= '2025-01-01'
ORDER BY id
LIMIT 1000;

如果过滤列与主键分布严重错位,应在预生产环境验证 chunk 行为,不要只看整条 SQL 的平均执行时间。

对于选择率高度不均匀的条件,可以先关闭按时间自动调整,使用保守的固定 chunk:

pt-online-schema-change \
  --host=db-node-1.example.net \
  --user=ptosc \
  --ask-pass \
  --alter "ADD COLUMN source_tag VARCHAR(32) NULL" \
  --where "tenant_id = 9001 AND created_at >= '2025-01-01'" \
  --chunk-index PRIMARY \
  --chunk-time 0 \
  --chunk-size 1000 \
  --max-load Threads_running=40 \
  --critical-load Threads_running=80 \
  --pause-file /tmp/ptosc.pause \
  --set-vars "lock_wait_timeout=5,innodb_lock_wait_timeout=5" \
  --dry-run \
  D=app,t=orders

这里的关键点是:

  • --chunk-time 0 禁用基于目标时间的 chunk 自动调整;
  • --chunk-size 1000 给每批复制设置一个保守起点,具体值必须压测;
  • --pause-file 允许运维人员通过创建文件暂停任务;
  • --max-load 和 --critical-load 仍用于保护本地 MySQL 负载;
  • --dry-run 必须成功后才能换成 --execute。

需要暂停时可以执行:

touch /tmp/ptosc.pause

确认集群恢复后继续:

rm -f /tmp/ptosc.pause

不同 Percona Toolkit 版本对 Galera Flow Control 的直接检测选项可能不同。先检查当前版本是否提供相关参数,再按该版本文档配置,不要直接复制未知单位的阈值:

pt-online-schema-change --version
pt-online-schema-change --help | grep -i -C 2 flow

迁移期间监控什么

在每个 Galera 节点上持续观察集群状态,而不是只检查发起 DDL 的节点:

watch -n 2 'mysql --defaults-extra-file=/etc/mysql/monitor.cnf --batch --raw -e "
SHOW GLOBAL STATUS
WHERE Variable_name IN (
  '\''wsrep_ready'\'',
  '\''wsrep_local_state_comment'\'',
  '\''wsrep_local_recv_queue'\'',
  '\''wsrep_local_recv_queue_avg'\'',
  '\''wsrep_local_recv_queue_max'\'',
  '\''wsrep_flow_control_sent'\'',
  '\''wsrep_flow_control_recv'\'',
  '\''wsrep_flow_control_paused'\''
);"'

/etc/mysql/monitor.cnf 可以这样准备,并限制文件权限:

[client]
host=db-node-1.example.net
user=monitor
password=REPLACE_WITH_SECRET
chmod 600 /etc/mysql/monitor.cnf

重点关注接收队列是否持续增长、Flow Control 计数是否在迁移期间增加,以及业务延迟是否与 chunk 提交同步出现。累计状态值应通过时间窗口内的增量判断,不能只看某一次快照。

上线前检查清单

  • 明确 --where 是数据保留规则,而不是普通限速手段。
  • 比较总行数与条件命中行数,并保存迁移前校验结果。
  • 检查过滤条件在 chunk 索引上的分布,而不只是整体选择率。
  • 在接近生产数据分布的环境中运行 --dry-run 和演练。
  • 对稀疏、突变的数据优先考虑固定且保守的 chunk。
  • 同时监控所有 Galera 节点的接收队列和 Flow Control。
  • 为任务配置暂停手段、负载阈值和明确的中止标准。
  • 在低峰期执行,并确认备份、分析任务不会拖慢某个节点。

动态 chunk 本身并不是问题。真正危险的是把“前一个 chunk 很快”误判成“下一个 chunk 也安全”。当 --where 改变了数据密度,而 Galera 又把单节点压力扩散为集群级暂停时,稳定性往往来自更小、更可预测的事务,而不是追求最快的复制速度。


相关推荐