结论先行:outline/SPM 这类绑定是「按 SQL 文本(digest)匹配 + 锁住一个具体索引/计划」,它对 DDL 加列、minor 升级是"文本不变即继续命中"的——所以它不会自己失效,但也正因如此,它会把"统计信息偏移后的次优计划"锁死成强制次优。你要防的不是"绑定失效",而是"绑定锁住了一个不再最优的计划"。 这两件事的监控口径完全不同,你的 rows_examined 哨兵只覆盖了前者的一半。下面按你的四个问题给可落地的东西。
一、失效场景枚举(MySQL 8.0 + 云 RDS outline 实现)
先分清两个层面:
-
绑定是否还命中:RDS 的
CREATE OUTLINE和原生 SPM 一样,匹配键是 SQL 的 digest(去字面量后的文本指纹),不是表结构 hash。所以:ALTER TABLE ADD COLUMN:SQL 文本没变 → 绑定继续命中,索引idx_tenant_period还在 → 计划不变。✅ 安全。- minor 升级(8.0.36→8.0.42):digest 不变 → 绑定继续命中,且因为锁的是"走 idx_tenant_period"这个结论,优化器成本模型再怎么微调都被你的绑定压住 → 计划不变。✅ 这正是绑定的价值:升级不漂移。
- 会真正让绑定失效/失效风险的操作:① SQL 文本变了(改 SQL、加了 hint、参数顺序/写法变化导致 digest 变);② 索引被 drop/rename/rebuild 后结构变(如
ALTER TABLE FORCE重建、DROP INDEX+重建),绑定指向的索引名若变了则命中失效;③ outline 本身被 drop、或 RDS outline 数量超限被截断(见第四点);④ 主备切换/克隆实例,outline 元数据是否随 DTS 同步需单独确认(云厂商行为不一,务必在切换演练里验证)。 - 所以"DDL 加列"和"minor 升级"在你的场景里都不会让绑定失效,你担心的方向反了——真正要盯的是下面第二类。
-
绑定锁住的计划是否已次优(你真正的风险):
- 统计信息偏移:UNBILLED 占比 3%→40%,
idx_tenant_period的基数估算没跟上,此时"走这个索引"从最优退化成次优(比如从 range 退化成 index full scan,rows_examined 200→5000,你的哨兵阈值 50 万抓不到)。绑定把它锁死 → 强制次优。这是你的头号风险。 - 索引选择性退化:某列基数下降、或新加了更优的复合索引但绑定还锁旧索引。
- 这类失效没有任何"绑定消失"的信号,表现只能是"性能变差但绑定看起来正常"。
- 统计信息偏移:UNBILLED 占比 3%→40%,
二、健康度检测方案(三态区分 + 可落地 SQL)
你要区分三态,建议用一张定时跑的 SQL 把三者拆开。核心思路:绑定的"命中"是确定的(outline 命中即锁死),所以问题退化为"被锁住的这个索引,在当前统计信息下是不是仍最优"。
三态判定(每 N 分钟跑一次):
-- 1) 绑定是否命中("绑定生效" vs "绑定失效")
-- outline 命中时,执行计划里会出现你锁的索引;失效时会回退到优化器自选
SELECT
digest_text,
COUNT_STAR,
AVG_ROWS_EXAMINED,
MAX_ROWS_EXAMINED,
SUM_TIMER_WAIT/1e9 AS total_ms
FROM performance_schema.events_statements_summary_by_digest
WHERE digest_text LIKE '%t_invoice_item%'
AND digest_text LIKE '%tenant_id%'
ORDER BY COUNT_STAR DESC;
-- 2) "绑定生效但次优" 的信号:rows_examined 相对历史基线漂移
-- 用 7 天分位数做基线,而不是固定 50 万阈值
-- 关键:对比 AVG_ROWS_EXAMINED 与 7 天 P50 的比值,>3x 且 <全表量级 => 次优而非失效
-- 3) 被锁索引当前是否仍最优:直接 EXPLAIN 看执行计划 + 对比"不锁"时的计划
EXPLAIN FORMAT=JSON
SELECT ... FROM t_invoice_item
WHERE tenant_id=? AND billing_period=? AND status=?;
-- 对比:把 outline 临时 drop 后 EXPLAIN 同一 SQL,若两者 chosen index 不同 => 绑定锁住了次优
三态判定表:
- 绑定生效且最优:EXPLAIN chosen index = 你锁的索引 = 不锁时的最优索引;rows_examined 在基线内。
- 绑定生效但次优:chosen index = 你锁的索引 ≠ 不锁时的最优索引(或 rows_examined 漂移到次优区)。→ 触发"重绑"流程。
- 绑定已失效:EXPLAIN chosen index ≠ 你锁的索引(回退到优化器自选),或 outline 表里查不到该 digest。→ 触发"失效告警"。
关键升级点:把你的"rows_examined 突增 >50 万"二元阈值,改成"rows_examined 相对 7 天 P50 的倍数 + EXPLAIN chosen index 是否等于锁定的索引"双信号。前者抓"次优",后者抓"失效"。
落地建议:写一个 outline_health 巡检脚本,每 5 分钟对每条绑定的 SQL:①查 outline 表确认还在;②EXPLAIN 确认 chosen index 与锁定值一致;③对 events_statements_summary_by_digest 算 rows_examined 7 天分位数,超 3x 且 chosen index 仍是锁定值 → 报"次优";chosen index 变了 → 报"失效"。三态各走不同告警级别。
三、安全解绑/重绑流程(含灰度)
标准流程(云 RDS 无原生"按连接灰度解绑",靠 SQL 层面灰度):
- 取证:EXPLAIN 对比"锁 idx_tenant_period"vs"不锁"两个计划,确认不锁时更优的索引是哪个(比如新加的
idx_tenant_status_period)。 - 灰度重绑:
- 方案 A(推荐):不直接 drop 旧 outline,而是 drop 旧 outline + 立即 CREATE 新 outline 指向更优索引,两步在同一个短窗口内完成。RDS 的 outline 变更是 DDL 级、秒级生效,中间无"无绑定"真空期的概率取决于是否同事务——云 RDS 通常不支持跨语句事务包 DDL,所以用"先建新后删旧":如果 outline 允许同 digest 两条,先
CREATE OUTLINE指向新索引(新 digest 命中优先生效),观察 10 分钟 EXPLAIN + P99,再DROP OUTLINE旧的。若同 digest 只允许一条,则接受 drop→create 的秒级真空(此时回退到优化器自选,若优化器此时已选优索引则无损)。 - 方案 B(真灰度):如果业务 SQL 能加 hint,改用 optimizer hint 灰度——对 5% 流量的 SQL 加
/*+ INDEX(t idx_tenant_status_period) */,对比 P99,确认新索引优后全量。
- 方案 A(推荐):不直接 drop 旧 outline,而是 drop 旧 outline + 立即 CREATE 新 outline 指向更优索引,两步在同一个短窗口内完成。RDS 的 outline 变更是 DDL 级、秒级生效,中间无"无绑定"真空期的概率取决于是否同事务——云 RDS 通常不支持跨语句事务包 DDL,所以用"先建新后删旧":如果 outline 允许同 digest 两条,先
- 验证:重绑后跑 30 分钟,确认 events_statements_summary_by_digest 的 rows_examined 回落到基线、P99 下降、且 EXPLAIN chosen index = 新锁定索引。
- 回滚:保留旧 outline 的 SQL 文本,随时可 drop 新绑旧绑回滚。
"灰度解绑只对新连接生效":MySQL/RDS 的 outline 是全局 digest 级,没有 per-connection 的灰度开关。真灰度只能靠 ①hint 级(SQL 侧)②影子流量(只读副本上跑对比)。别指望 outline 本身支持按连接灰度。
四、长期维护策略
- 绑定不是"绑了就永远在":它锁的是"当时的最优",而统计信息、数据分布、索引集合都在变。建议 review 频率 = 数据分布变化频率。你的场景 UNBILLED 占比会随业务波动,建议每次批跑翻转 status 后 + 每周各 review 一次绑定的三态健康度(用第二点的脚本)。
- 绑定数量本身是风险:
- RDS outline 有数量上限(云厂商按实例规格定,通常几百到几千条),超限后新 outline 可能不生效或被截断——务必查你实例规格对应的上限,并把"当前 outline 数 / 上限"纳入监控。
- 匹配开销:outline 是 digest 哈希匹配,单条 SQL 命中是 O(1),数量本身不显著影响单条 SQL 性能,但数量越多,维护成本和"锁死次优"的概率越高(每条都是潜在次优锁定)。
- 经验阈值:单库绑定数 > 50 条就该 review 一轮"是否还有必要",把已被索引优化/SQL 改写好解决的绑定逐步下线。绑定是"治标",加对索引/改 SQL 才是"治本",绑定数长期只增不减说明你在用绑定掩盖索引设计问题。
一句话总结:绑定防的是"漂移",防不了"漂移后的最优变了"。监控要从"绑定还在不在"升级到"被锁住的索引当前还优不优",用 EXPLAIN chosen index + rows_examined 7 天分位数双信号,三态分别告警;解绑用"先建新后删旧 + hint 灰度",review 频率跟数据分布走,绑定数本身要定期瘦身。
(参考了你引用的 id=1033 统计信息刷新时机、id=2258 四条路取舍、id=2271 计划稳定性度量——本文是"绑定上线后运维维护"视角的补充,不重复那三篇。)