查询没改、代码没动,却突然变慢?TiDB SQL Plan Binding 让 DBA 不改应用就能锁计划
> 生产环境里最阴险的性能问题之一:查询跑了几个月都稳,某天突然开始爬。代码没改、SQL 没动,变的是优化器选的执行计划。TiDB 内置的 SQL Plan Management(SPM)给了 DBA 一个不用动应用的解法。原文来自 Mydbops(作者 Kabilesh,两次获 TiDB Community Champion)。
执行计划回归:问题从哪来
TiDB 的代价优化器会为每条查询生成并评估多条执行路径,用统计信息、数据分布、谓词选择率、基数估计、ORDER BY/LIMIT 算成本挑最便宜的。多数时候很可靠——但当统计信息过期、数据分布漂移、复杂查询基数估不准时,优化器就会挑一条明显更贵的路。
SQL Plan Binding 的作用:把优化器 hint 透明地附加到进入的 SQL 上。应用继续发它原本”干净”的无 hint 查询,TiDB 在背后匹配查询、附加绑定的 hint、产出你要的计划。事故现场不用等代码评审、QA、发布和回滚计划。
标准操作流程
第 1 步:先测 hint,别直接绑。 用 EXPLAIN ANALYZE 直接跑带 hint 的语句,紧接着 SHOW WARNINGS——hint 拼错、索引不存在、与查询结构冲突都会在这里暴露。优化器应用不了的 hint,绑了也白绑。
第 2 步:在明确的库上下文里创建全局绑定。 先 USE 再建绑定,绑定记录会把表名限定到对应库(如 employee.users)。
匹配靠 SQL 规范化,不是逐字符比对:TiDB 会剥掉字面量常量、解析对象命名空间、去多余空白,形成 SQL digest——所以一个绑定覆盖同一查询的所有参数变体。
验证绑定存在:SHOW GLOBAL BINDINGS;,重点看 Status(enabled)和 Default_db 是否对上执行库。
确认查询真的用上了:
SELECT @@LAST_PLAN_FROM_BINDING; ——返回 1 才是真用上了绑定可以覆盖应用内嵌 hint:管理员绑定的优先级高于代码里的 hint。应急时是好事,但也意味着开发者看代码看不出计划为何如此——变更记录必须跟上。
删除绑定:schema 变更、加索引、版本升级后绑定多余了就 drop,TiDB 将全局绑定记录标记删除并传播到所有节点。
最容易误解的点:GLOBAL 不是”全库同名表”
GLOBAL 不意味着这个绑定匹配所有库里叫 users 的表。它定义的是绑定的生命周期和作用域跨节点(集群级 vs 会话级),配合创建时记录的默认库上下文来限定表。
今天的好计划,明天可能变坏计划
这是全文最重要的一条运维规则,作者给了一个真实场景:
无限期锁死的计划,会变成你当初想解决的那个瓶颈。 每个绑定都要当”有生命周期的运维对象”管:发现回归 → 找回已知好计划 → 测 hint → 建绑定 → 验证生效 → 盯延迟/RU/扫描行数 → 定期复审 → 不需要了就解绑。
进阶玩法:TiDB 的 Statement Summary 表存了历史执行,可以直接拿早上跑得快时的 plan digest 反向绑定,TiDB 自动重建所需 hint(复杂多表 join 仍要 EXPLAIN 复核)。
事故收尾 Checklist
EXPLAIN ANALYZE,对比耗时、扫描行数、扫描键数、内存、RUSHOW WARNINGS 干净USE SHOW GLOBAL BINDINGS 里 Status 为 enabledSELECT @@LAST_PLAN_FROM_BINDING 返回 1
小夏点评
@@LAST_PLAN_FROM_BINDING 这个验证开关很多 DBA 不知道:绑定存在 ≠ 绑定生效,上线后必须实测一次。