首批通过分布式安全可靠测评,为关键业务系统打造
同一条 SQL 使用不同的参数执行时快时慢
更新时间:2026-05-14 07:41
适用版本
OceanBase 数据库所有版本。
问题现象
同一条 SQL 使用不同的参数执行时快时慢。
问题原因
为了让执行更加高效,OceanBase 数据库引入了计划缓存机制来减少计划的生成次数。具体实现的方式是对于任意 SQL,首先将 SQL 中所有的常量值参数化为通配符 ?。参数化后 SQL 文本相同的 SQL 会使用同一个计划,例如如下 Q1 和 Q2 两条 SQL 都会参数化为 Q3 的形式,因此会共用同一个计划。
create table t1 (c1 int, c2 int, c3 int, key idx1(c1), key idx2(c2));
Q1: select * from t1 where c1 = 1 and c2 = 1;
Q2: select * from t1 where c1 = 2 and c2 = 2;
-- 参数化
Q3: select * from t1 where c1 = ? and c2 = ?;
那么参数化后的 Q3 对应的计划是怎么生成的呢?这里 OceanBase 数据库采用了先到先生成的原则,如果 server 先收到了 Q1,那么就以 Q1 的参数 c1 = 1 and c2 = 1 生成计划。如果 server 先收到了 Q2,那么就以 Q2 的参数 c1 = 2 and c2 = 2 生成计划。
也就是说 OceanBase 数据库的计划缓存是参数不敏感的,但是计划的生成是参数敏感的。这自然而然的引申出了一个问题,参数敏感导致生成了不稳定的计划。我们依然以 Q3 为例,假设 c1 = 1 的过滤性极佳,使用该值过滤后数据只剩 10 行,但 c1 = 2 的过滤性非常差,使用该值过滤后数据会剩 100000 行;c2 中 c2 = 1 和 c2 = 2 的过滤性都还不错,过滤后数据会剩 100 行。那么如果 server 先收到了 Q1,那么很显然会生成走 idx1 的计划,因为 idx1 只要在索引上扫描 10 行然后回表 10 行即可,而 idx2 需要再索引上扫描 100 行和回表 100 行。但是一旦 Q2 使用了这个计划,其执行性能就会变得比较差,因为 c1 = 2 在 idx1 上需要扫描 100000 行。相对的,如果 server 先收到了 Q2,就会生成走 idx2 的计划,即使后续 Q1 复用这个计划,其执行性能也不会出现波动。
这就是为什么我们会发现在数据倾斜的场景下,有些 SQL 会出现第一次执行很快,但是后续执行会变得很慢的原因。这一类问题通常会出现在 SQL 的计划重新生成时,由于用户无法控制生成计划时 server 会先收到那条 SQL,因此问题往往表现出随机性,即有时很长一段时间内执行都很正常,有时突然执行性能变得很差。目前 OceanBase 数据库中重新生成计划的条件如下。
- 用户手动或者 server 自动收集了统计信息。
- 表上出现了 DDL。
- 计划缓存的内存使用过大进行了自动淘汰。
解决方法
- 找出执行性能更加稳定的计划,并对 SQL 绑定 OUTLINE。例如上面的例子中可以对 Q3 这一类 SQL 绑定走索引 idx2 的 OUTLINE。
- 新建一个更加合适的索引。例如上面的例子中可以新建一个 (c1,c2) 的复合索引。
- 找出性能抖动的 SQL,刷掉这条 SQL 当前的执行计划。尝试让优化器根据另外一组参数重新生成计划。
- 在 OceanBase 数据库 V4.1 及之后版本版本中可以使用 SPM 功能解决大小账号导致的计划走错问题。需要注意的是如果使用 SPM 功能需要保证稳定的计划能够在一开始被加入到基线计划中。