1.问题描述
- 数据库版本:OceanBase 3.2.3.3
下面这个 SQL 执行超过 1000 秒……
本文用这个例子,谈谈标量子查询慢的原因和优化方法。
2.分析过程
执行计划如下:
每个子算子的成本都不高,但总成本很高!
下面结合 SQL 语法语义进行解读。
首先,这个 SQL 从语法上分两部分:
- 标量子查询,即投影部分的子查询。
- 外部查询,即 FROM 子句的关联查询和子查询。
因此,这个 SQL 的执行逻辑是(也就是执行计划里的 0 号 SUBPLAN FILTER 算子):
- 先执行外部查询,得到 结果集 r(执行计划中的 1-6 号算子)。
- 再执行标量子查询,从 结果集 r 中取一行数据,带入到标量子查询中执行(执行计划中的 7-18 号算子)。
- 重复上一步,直到循环取完最后一行数据。
为了定位 SQL 到底慢在哪一步?让我们继续拆解。
- 先拆出外部查询(即对应的 1-6 号算子部分),单独执行很快得到结果 13 万行,也就意味着所有标量子查询都需要执行 13 万次。
- 从执行计划来看,7、8、9、18 号算子对应的 4 个标量子查询都可以走索引,效率较高。只保留外部查询和这 4 个标量子查询,执行耗时很短。
- 重点是 10、14 两个算子,对应的 2 个标量子查询除了和外表关联外,本身内部还有 o、k 这 2 张表关联,这两张表要做多少次关联?13万次! 很明显这里效率会很低。
SQL 中 10、14 两个算子对应的标量子查询如下,还可以再拆解 SQL,单独只做一次 、k 表的关联查询(如下标黄部分)要 200 毫秒:
3.结论
标量子查询的执行计划只能是循环嵌套连接,也就是 SUBPLAN FILTER 算子(等同于 NESTED-LOOP JOIN 执行逻辑),它的执行效率取决于两个因素:
- 外部查询的结果集大小
- 子查询的效率
因此只有当外部查询结果集不大,并且子查询的关联字段有高效索引时,执行效率才高。如果关联字段没有索引,优化器也没法像 JOIN 语法一样使用 HASH JOIN 算子,执行效率很差。
在上面这个慢 SQL 中,有两个标量子查询不只和外表关联,它内部还有关联查询,所以即使关联字段有索引,子查询单次执行的效率也受限,再加上要执行 13 万次,这个耗时就长了。所以这个 SQL 只能改写成 LEFT JOIN 来优化,这也是标量子查询的标准优化方法。
4.优化方案
这个 SQL 的标量子查询中有聚合函数,应该先 GROUP BY 聚合后再和外表关联,SQL(局部)改写如下:
改写后的执行计划如下(变成了使用 HASH OUTER JOIN 算法),可以看到。
成本 7.88 亿降到了 365 万,执行耗时降到 10 秒!
作者:胡呈清,爱可生 DBA 团队成员,擅长故障分析、性能优化,个人博客:[简书 | 轻松的鱼]