欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 美食 > 从1000s到10s,OceanBase 标量子查询的SQL优化实践

从1000s到10s,OceanBase 标量子查询的SQL优化实践

2025/6/30 10:46:17 来源:https://blog.csdn.net/OceanBaseGFBK/article/details/140721081  浏览:    关键词:从1000s到10s,OceanBase 标量子查询的SQL优化实践

本文作者:胡呈清,爱可生 DBA 团队成员,擅长故障分析、性能优化。

本文借助一个案例,探讨标量子查询执行慢的原因分析过程,以及相应的优化方案。

问题描述

  • 本案例的数据库版本为:OceanBase 3.2.3.3

这个案例里的SQL,执行时间已经超过1000秒。

selectrq.processinstid processinstid,rq.question_id questionId,rq.question_no questionNo,to_char(rq.rev_start_date, 'yyyy-MM-dd') revStartDate,(selecte.namefromewheree.category_code = 'REV_SOURCE'and e.code = rq.rev_source) revSource,(selecte.namefromewheree.category_code = 'QUESTION_TYPE'and e.code = rq.question_type) questionType,rq.question_summary questionSummary,rq.question_desc questionDesc,to_char(rq.question_discover_date, 'yyyy-MM-dd') questionDiscoverDate,rq.aud_project_type audProjectType,(selectd.dept_namefromdwhered.dept_id = rq.check_dept) checkDept,(selectto_char(wm_concat(distinct(k.org_name)))fromo,kwhereo.question_id = rq.question_idand o.ASC_ORG = k.org_idand o.REFORM_TYPE = '0') ascOrg,(selectto_char(wm_concat(distinct(k.dept_name)))fromo,fnd_dept_t kwhereo.question_id = rq.question_idand o.MAIN_REV_DEPT = k.dept_idand o.REFORM_TYPE = '0') mainRevDept,(selecte.namefromewheree.category_code = 'REV_FINISH_STATE'and e.code = rq.rev_finish_state) revFinishState,to_char(rq.compliance_date, 'yyyy-MM-dd') complianceDATE
fromrqleft join REM_QUESTION_PLAN_T t on rq.question_id = t.question_idleft join fnd_org_t org on t.ASC_ORG = org.org_id
where1 = 1and rq.asc_org is nulland (t.asc_org in (selectf.org_idfromfwheref.org_type = 'G')or rq.created_by_org in (selectf.org_idfromfwheref.org_type = 'G'))and rq.company_type = 'G';

分析过程

执行计划如下:

===========================================================
|ID|OPERATOR          |NAME           |EST. ROWS|COST     |
-----------------------------------------------------------
|0 |SUBPLAN FILTER    |               |6283     |788388847|
|1 | SUBPLAN FILTER   |               |6283     |1325483  |
|2 |  HASH OUTER JOIN |               |8377     |210530   |
|3 |   TABLE SCAN     |RQ             |7966     |77932    |
|4 |   TABLE SCAN     |T              |152919   |59150    |
|5 |  TABLE SCAN      |F              |440      |2763     |
|6 |  TABLE SCAN      |F              |440      |2763     |
|7 | TABLE SCAN       |E(SYS_C0011218)|1        |92       |
|8 | TABLE SCAN       |E(SYS_C0011218)|1        |92       |
|9 | TABLE GET        |D              |1        |46       |
|10| SCALAR GROUP BY  |               |1        |62483    |
|11|  NESTED-LOOP JOIN|               |1        |62483    |
|12|   TABLE SCAN     |O              |1        |62468    |
|13|   TABLE GET      |K              |1        |28       |
|14| SCALAR GROUP BY  |               |1        |62483    |
|15|  NESTED-LOOP JOIN|               |1        |62483    |
|16|   TABLE SCAN     |O              |1        |62468    |
|17|   TABLE GET      |K              |1        |27       |
|18| TABLE SCAN       |E(SYS_C0011218)|1        |92       |
===========================================================

每个子算子的成本都不高,但总成本很高!

下面,我们将根据SQL的语法和语义进行解析。

首先,该SQL语句从语法上可以划分为两部分:

  1. 标量子查询,即投影部分的子查询。
  2. 外部查询,即 FROM 子句的关联查询和子查询。

因此,这个 SQL 的执行逻辑是(也就是执行计划里的 0 号 SUBPLAN FILTER 算子):

  1. 先执行外部查询,得到 结果集 r(执行计划中的 1-6 号算子)。
  2. 再执行标量子查询,从 结果集 r 中取一行数据,带入到标量子查询中执行(执行计划中的 7-18 号算子)。
  3. 重复上一步,直到循环取完最后一行数据。

为了定位 SQL 到底慢在哪一步?让我们继续拆解。

  • 先拆出外部查询(即对应的 1-6 号算子部分),单独执行很快得到结果 13 万行,也就意味着所有标量子查询都需要执行 13 万次。
  • 从执行计划来看,7、8、9、18 号算子对应的 4 个标量子查询都可以走索引,效率较高。只保留外部查询和这 4 个标量子查询,执行耗时很短。
  • 重点是 10、14 两个算子,对应的 2 个标量子查询除了和外表关联外,本身内部还有 o、k 这 2 张表关联,这两张表要做多少次关联?13万次! 很明显这里效率会很低。

SQL 中 10、14 两个算子对应的标量子查询如下,还可以再拆解 SQL,单独只做一次 、k 表的关联查询(如下标黄部分)要 200 毫秒:

select
xxx,
(selectto_char(wm_concat(distinct(k.org_name)))fromREM_QUESTION_PLAN_T o,fnd_org_t kwhereo.question_id = rq.question_idand o.ASC_ORG = k.org_idand o.REFORM_TYPE = '0') ascOrg,(selectto_char(wm_concat(distinct(k.dept_name)))fromREM_QUESTION_PLAN_T o,fnd_dept_t kwhereo.question_id = rq.question_idand o.MAIN_REV_DEPT = k.dept_idand o.REFORM_TYPE = '0') mainRevDept,xxxfrom t(外部查询,结果有 13 万行);

结论

标量子查询的执行计划只能是循环嵌套连接,也就是 SUBPLAN FILTER 算子(等同于 NESTED-LOOP JOIN 执行逻辑),它的执行效率取决于两个因素:

  • 外部查询的结果集大小
  • 子查询的效率

因此,只有当外部查询的结果集规模较小,并且子查询所关联的字段上配置了高效的索引时,整个查询的执行效率才会较高。反之,如果关联字段未建立索引,优化器便无法像处理JOIN语句那样,有效地利用HASH JOIN等高效算子,从而导致查询的执行效率显著降低。

在上面这个慢 SQL 中,有两个标量子查询不只和外表关联,它内部还有关联查询,所以即使关联字段有索引,子查询单次执行的效率也受限,再加上要执行 13 万次,这个耗时就长了。所以这个 SQL 只能改写成 LEFT JOIN 来优化,这也是标量子查询的标准优化方法。

优化方案

这个 SQL 的标量子查询中有聚合函数,应该先 GROUP BY 聚合后再和外表关联,SQL(局部)改写如下:

with t1 as (selecto.question_id,to_char(wm_concat(distinct(k.org_name))) as org_namefromREM_QUESTION_PLAN_T o,fnd_org_t kwhereo.ASC_ORG = k.org_idand o.REFORM_TYPE = '0'group byo.question_id
),
t2 as (selecto.question_id,to_char(wm_concat(distinct(k.dept_name))) as dept_namefromREM_QUESTION_PLAN_T o,fnd_dept_t kwhereo.MAIN_REV_DEPT = k.dept_idand o.REFORM_TYPE = '0'group byo.question_id
)
select
xxx,
t1.org_name as ascOrg,
t2.dept_name as mainRevDept,
xxxfrom t(外部查询,结果有 13 万行)left join t1 on t.question_id=t1.question_idleft join t2 on t.question_id=t2.question_id;

改写后的执行计划如下(变成了使用 HASH OUTER JOIN 算法),可以看到成本 7.88 亿降到了 365 万,执行耗时降到 10 秒!

=============================================================
|ID|OPERATOR              |NAME           |EST. ROWS|COST   |
-------------------------------------------------------------
|0 |SUBPLAN FILTER        |               |6318     |3653489|
|1 | MERGE GROUP BY       |               |6318     |1636701|
|2 |  SORT                |               |6318     |1632074|
|3 |   SUBPLAN FILTER     |               |6318     |1613799|
|4 |    HASH OUTER JOIN   |               |8424     |492531 |
|5 |     HASH OUTER JOIN  |               |8377     |331672 |
|6 |      MERGE OUTER JOIN|               |7966     |198317 |
|7 |       TABLE SCAN     |RQ             |7966     |77932  |
|8 |       SUBPLAN SCAN   |T2             |2351     |119098 |
|9 |        MERGE GROUP BY|               |2351     |119062 |
|10|         SORT         |               |2352     |118658 |
|11|          HASH JOIN   |               |2352     |113818 |
|12|           TABLE SCAN |K              |22268    |8614   |
|13|           TABLE SCAN |O              |76460    |60075  |
|14|      TABLE SCAN      |T              |152919   |59150  |
|15|     SUBPLAN SCAN     |T1             |76415    |118014 |
|16|      HASH JOIN       |               |76415    |116865 |
|17|       TABLE SCAN     |K              |7033     |2721   |
|18|       TABLE SCAN     |O              |76460    |60075  |
|19|    TABLE SCAN        |F              |440      |2763   |
|20|    TABLE SCAN        |F              |440      |2763   |
|21| TABLE SCAN           |E(SYS_C0011218)|1        |92     |
|22| TABLE SCAN           |E(SYS_C0011218)|1        |92     |
|23| TABLE GET            |D              |1        |46     |
|24| TABLE SCAN           |E(SYS_C0011218)|1        |92     |
=============================================================

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词