美文网首页
SQL优化:标量子查询改写案例1

SQL优化:标量子查询改写案例1

作者: 轻松的鱼 | 来源:发表于2023-12-28 15:04 被阅读0次

问题描述

数据库版本:OceanBase3.2.3.3
下面这个SQL执行超过1000秒,本文用这个例子谈谈标量子查询慢的原因和优化方法。

select
  rq.processinstid processinstid,
  rq.question_id questionId,
  rq.question_no questionNo,
  to_char(rq.rev_start_date, 'yyyy-MM-dd') revStartDate,
  (
    select
      e.name
    from
      e
    where
      e.category_code = 'REV_SOURCE'
      and e.code = rq.rev_source
  ) revSource,
  (
    select
      e.name
    from
      e
    where
      e.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,
  (
    select
      d.dept_name
    from
      d
    where
      d.dept_id = rq.check_dept
  ) checkDept,
  (
    select
      to_char(wm_concat(distinct(k.org_name)))
    from
      o,
      k
    where
      o.question_id = rq.question_id
      and o.ASC_ORG = k.org_id
      and o.REFORM_TYPE = '0'
  ) ascOrg,
  (
    select
      to_char(wm_concat(distinct(k.dept_name)))
    from
      o,
      fnd_dept_t k
    where
      o.question_id = rq.question_id
      and o.MAIN_REV_DEPT = k.dept_id
      and o.REFORM_TYPE = '0'
  ) mainRevDept,
  (
    select
      e.name
    from
      e
    where
      e.category_code = 'REV_FINISH_STATE'
      and e.code = rq.rev_finish_state
  ) revFinishState,
  to_char(rq.compliance_date, 'yyyy-MM-dd') complianceDATE
from
  rq
  left join REM_QUESTION_PLAN_T t on rq.question_id = t.question_id
  left join fnd_org_t org on t.ASC_ORG = org.org_id
where
  1 = 1
  and rq.asc_org is null
  and (
    t.asc_org in (
      select
        f.org_id
      from
        f
      where
        f.org_type = 'G'
    )
    or rq.created_by_org in (
      select
        f.org_id
      from
        f
      where
        f.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万次!很明显这里效率会很低

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

select
xxx,
(
    select
      to_char(wm_concat(distinct(k.org_name)))
    from
      REM_QUESTION_PLAN_T o,
      fnd_org_t k
    where
      o.question_id = rq.question_id
      and o.ASC_ORG = k.org_id
      and o.REFORM_TYPE = '0'
  ) ascOrg,
  (
    select
      to_char(wm_concat(distinct(k.dept_name)))
    from
      REM_QUESTION_PLAN_T o,
      fnd_dept_t k
    where
      o.question_id = rq.question_id
      and o.MAIN_REV_DEPT = k.dept_id
      and o.REFORM_TYPE = '0'
  ) mainRevDept,
  xxx
  from t(外部查询,结果有13万行);

结论

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

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

因此只有当外部查询结果集不大,并且子查询的关联字段有高效索引时,执行效率才高。如果关联字段没有索引,优化器也没法像 join 语法一样使用 hash join 算子,执行效率很差。
在上面这个慢SQL中,有两个标量子查询不只和外表关联,它内部还有关联查询,所以即使关联字段有索引,子查询单次执行的效率也受限,再加上要执行 13万次,这个耗时就长了。所以这个SQL只能改写成 left join 来优化,这也是标量子查询的标准优化方法。

优化方案

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

with t1 as (
  select
    o.question_id,
    to_char(wm_concat(distinct(k.org_name))) as org_name
  from
    REM_QUESTION_PLAN_T o,
    fnd_org_t k
  where
    o.ASC_ORG = k.org_id
    and o.REFORM_TYPE = '0'
  group by
    o.question_id
),
t2 as (
  select
    o.question_id,
    to_char(wm_concat(distinct(k.dept_name))) as dept_name
  from
    REM_QUESTION_PLAN_T o,
    fnd_dept_t k
  where
    o.MAIN_REV_DEPT = k.dept_id
    and o.REFORM_TYPE = '0'
  group by
    o.question_id
)
select
xxx,
t1.org_name as ascOrg,
t2.dept_name as mainRevDept,
xxx
  from t(外部查询,结果有13万行)
  left join t1 on t.question_id=t1.question_id
  left 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     |
=============================================================

相关文章

  • Oracle查询优化改写技巧与案例 PDF高清版

    内容简介 《Oracle查询优化改写技巧与案例》不讲具体语法,只是以案例的形式介绍各种查询语句的用法。第1~4章是...

  • Mysql索引优化

    1、单表索引优化 单表索引优化分析 创建表 建表 SQL 表中的测试数据 查询案例 查询category_id为1...

  • 数据库中间件 Sharding-JDBC 源码分析 —— SQL

    1. 概述 本文分享SQL 改写的源码实现。主要涉及两方面: SQL 改写:改写 SQL,解决分库分表后,查询结果...

  • Java面试题:数据库优化策略有哪些?

    1、Sql优化主要优化的还是查询, 优化查询的话, 索引优化是最有效的方案。 首先要根据需求写出结构良好的SQL,...

  • sql优化的一般策略

    sql 优化的一般策略:索引优化,sql改写,参数优化,优化器 索引优化 以select * from vvsho...

  • SQL锁优化问题

    关于SQL锁。SQL优化少不了SQL锁优化 1.SQL常见锁有:共享锁 作用于查询操作(Select) 2.排他锁...

  • SQL 语句优化整理

    为了提高 SQL 查询效率,我们都会采取一切 SQL 语句的优化。 1 对查询进行优化,应尽量避免全表扫描,首先应...

  • 30种 SQL 语句优化,进阶必备(一)

    为了提高 SQL 查询效率,我们都会采取一切 SQL 语句的优化。 1 对查询进行优化,应尽量避免全表扫描,首先应...

  • 详解MySQL之SQL优化(1)

    MySQL学习笔记(6) SQL优化(1) 优化SQL的一般步骤 本文所涉及案例表来自MySQL的案例库sakil...

  • 12)sql优化

    优化sql的一般步骤1.发现问题 -> 分析执行计划 -> 优化索引 -> 改写sql 如以上方法还无法达到满意的...

网友评论

      本文标题:SQL优化:标量子查询改写案例1

      本文链接:https://www.haomeiwen.com/subject/foadndtx.html