优化案例1---用分析函数优化优化执行计划中的FILTER以及标量子查询

        今天在餐饮前端库抓出一条跑的很慢的分页语句,分页语句里没有对主表group by 这些禁忌,但是还跑6秒才跑完,逻辑读大概有  735634个buffers,立马给优化了一下。


SQL 语句如下:

 

SELECT *
  FROM (SELECT (SELECT COUNT(1)
                  FROM cy_product_sku ps
                 WHERE ps.product_code = pc.product_code
                   AND ps.isdel = 0) AS skuNum,
               pc.version,
               pc.product_code,
               pc.product_name,
               ps.price,
               pc.gross_weight,
               pc.quantity,
               pc.up_time,
               pb.name AS ppName,
               f_getcategory(pca.id) AS flName,
               ps.product_num,
               pt.name AS lxName,
               pc.MAINIMGURL
          FROM cy_product_code pc
          LEFT JOIN cy_product_brand pb
            ON pc.brand_id = pb.id
          LEFT JOIN cy_product_category pca
            ON pca.id = pc.category_id
          LEFT JOIN cy_product_type pt
            ON pc.type_id = pt.id
          LEFT JOIN cy_product_sku ps
            ON ps.product_code = pc.product_code
         WHERE pc.isup = 1
           AND pc.isdel = 0
           AND ps.product_sku =
               (SELECT MIN(product_sku)
                  FROM cy_product_sku
                 WHERE DEFAULT_SKU IN (2, 3)
                   AND isdel = 0
                   AND product_code = pc.product_code
                   AND price = (SELECT MIN(price)
                                  FROM cy_product_sku
                                 WHERE DEFAULT_SKU IN (2, 3)
                                   AND isdel = 0
                                   AND product_code = pc.product_code
                                 GROUP BY PRODUCT_CODE)
                 GROUP BY product_code)
         ORDER BY pc.up_time DESC)
 WHERE rownum <= 10


执行计划为:


------------------------------------------------------------------------------------------------------------
| Id  | Operation                        | Name                    | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                 |                         |     1 |  2892 |  2663   (1)| 00:00:32 |
|   1 |  SORT AGGREGATE                  |                         |     1 |    12 |            |          |
|*  2 |   TABLE ACCESS FULL              | CY_PRODUCT_SKU          |     2 |    24 |    10   (0)| 00:00:01 |
|*  3 |  COUNT STOPKEY                   |                         |       |       |            |          |
|   4 |   VIEW                           |                         |     1 |  2892 |  2663   (1)| 00:00:32 |
|*  5 |    SORT ORDER BY STOPKEY         |                         |     1 |   180 |  2663   (1)| 00:00:32 |
|*  6 |     FILTER                       |                         |       |       |            |          |
|*  7 |      HASH JOIN                   |                         |  1644 |   288K|   195   (0)| 00:00:03 |
|   8 |       TABLE ACCESS FULL          | CY_PRODUCT_SKU          |  2027 | 77026 |    10   (0)| 00:00:01 |
|*  9 |       HASH JOIN RIGHT OUTER      |                         |   776 |   107K|   185   (0)| 00:00:03 |
|  10 |        TABLE ACCESS FULL         | CY_PRODUCT_CATEGORY     |   532 |  2128 |     4   (0)| 00:00:01 |
|* 11 |        HASH JOIN RIGHT OUTER     |                         |   776 |   104K|   181   (0)| 00:00:03 |
|  12 |         TABLE ACCESS FULL        | CY_PRODUCT_BRAND        |    35 |   455 |     4   (0)| 00:00:01 |
|* 13 |         HASH JOIN RIGHT OUTER    |                         |   776 | 97000 |   177   (0)| 00:00:03 |
|  14 |          TABLE ACCESS FULL       | CY_PRODUCT_TYPE         |    31 |   496 |     4   (0)| 00:00:01 |
|* 15 |          TABLE ACCESS FULL       | CY_PRODUCT_CODE         |   776 | 84584 |   173   (0)| 00:00:03 |
|  16 |      SORT GROUP BY NOSORT        |                         |     1 |    35 |     3   (0)| 00:00:01 |
|* 17 |       TABLE ACCESS BY INDEX ROWID| CY_PRODUCT_SKU          |     1 |    35 |     3   (0)| 00:00:01 |
|* 18 |        INDEX RANGE SCAN          | IDX_CY_PRO_SK_ISD_PRICE |     4 |       |     1   (0)| 00:00:01 |
|  19 |         SORT GROUP BY NOSORT     |                         |     1 |    19 |    10   (0)| 00:00:01 |
|* 20 |          TABLE ACCESS FULL       | CY_PRODUCT_SKU          |     1 |    19 |    10   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("PS"."PRODUCT_CODE"=:B1 AND "PS"."ISDEL"=0)
   3 - filter(ROWNUM<=10)
   5 - filter(ROWNUM<=10)
   6 - filter("PS"."PRODUCT_SKU"= (SELECT MIN("PRODUCT_SKU") FROM "CY_PRODUCT_SKU" "CY_PRODUCT_SKU"
              WHERE "PRICE"= (SELECT MIN("PRICE") FROM "CY_PRODUCT_SKU" "CY_PRODUCT_SKU" WHERE "PRODUCT_CODE"=:B1
              AND ("DEFAULT_SKU"=2 OR "DEFAULT_SKU"=3) AND "ISDEL"=0 GROUP BY "PRODUCT_CODE") AND "ISDEL"=0 AND
              "PRODUCT_CODE"=:B2 AND ("DEFAULT_SKU"=2 OR "DEFAULT_SKU"=3) GROUP BY "PRODUCT_CODE"))
   7 - access("PS"."PRODUCT_CODE"="PC"."PRODUCT_CODE")
   9 - access("PCA"."ID"(+)=TO_NUMBER("PC"."CATEGORY_ID"))
  11 - access("PB"."ID"(+)=TO_NUMBER("PC"."BRAND_ID"))
  13 - access("PT"."ID"(+)=TO_NUMBER("PC"."TYPE_ID"))
  15 - filter("PC"."ISUP"=1 AND "PC"."ISDEL"=0)
  17 - filter("PRODUCT_CODE"=:B1 AND ("DEFAULT_SKU"=2 OR "DEFAULT_SKU"=3))
  18 - access("ISDEL"=0 AND "PRICE"= (SELECT MIN("PRICE") FROM "CY_PRODUCT_SKU" "CY_PRODUCT_SKU"
              WHERE "PRODUCT_CODE"=:B1 AND ("DEFAULT_SKU"=2 OR "DEFAULT_SKU"=3) AND "ISDEL"=0 GROUP BY
              "PRODUCT_CODE"))
  20 - filter("PRODUCT_CODE"=:B1 AND ("DEFAULT_SKU"=2 OR "DEFAULT_SKU"=3) AND "ISDEL"=0)

可以看到这个SQL 有三个问题

(1) select  里面有一个标量子查询 CY_PRODUCT_SKU 表将会扫描N次

(2) 这个分页框架是有问题的,这种分页框架,将会将所有的数据全部查询出来以后,然后order by 排序,最后一步再取前10行,也进而导致了 标量子查询里面的表被扫描N多次。

(3)执行计划里面有一个FILTER,而FILTER的第二个儿子,即ID=16往下的部分,将会被执行N多次,从SQL中看出,引起FILTER的原因就是这一段:

原SQL 里面的部分是:

SELECT MIN(product_sku)
                  FROM cy_product_sku ps1 
                 WHERE ps1.DEFAULT_SKU IN (2, 3)
                   AND ps1.isdel = 0
                   AND product_code = pc.product_code  -----和外面的pc 表根据product_code 关联 先去掉
                   AND ps1.price = (SELECT MIN(ps2.price)
                                  FROM cy_product_sku ps2
                                 WHERE ps2.DEFAULT_SKU IN (2, 3)
                                   AND ps2.isdel = 0
                                   AND ps2.product_code = ps1.product_code
                                 GROUP BY ps2.PRODUCT_CODE)
                 GROUP BY ps1.product_code

需求就是:访问 cy_product_sku 根据 product_code 分组,求出 每组 product_code 里面price 最小的 product_sku ,如果有两个不同的 product_sku 有相同的最低price 那么就取 product_sku 最小的那个。取完以后 拿 各组的 product_code,product_sku 和外面的进行关联。

等价的意思就是:

SELECT MIN(product_sku), ps1.product_code
  FROM cy_product_sku ps1
 WHERE ps1.DEFAULT_SKU IN (2, 3)
   AND ps1.isdel = 0
   AND ps1.price = (SELECT MIN(ps2.price)
                      FROM cy_product_sku ps2
                     WHERE ps2.DEFAULT_SKU IN (2, 3)
                       AND ps2.isdel = 0
                       AND ps2.product_code = ps1.product_code
                     GROUP BY ps2.PRODUCT_CODE)
 GROUP BY ps1.product_code


我将这一部分完全可以用分析函数来代替,只扫描一次CY_PRODUCT_SKU 表就可以获取到需要的数据:

等价改写:

select product_sku, product_code
  from (SELECT ROW_NUMBER() OVER(PARTITION BY product_code ORDER BY price, product_sku) ROW_NUM,
               product_sku,
               product_code
          FROM cy_product_sku
         WHERE DEFAULT_SKU IN (2, 3)
           AND isdel = 0) RS
 WHERE RS.ROW_NUM = 1;


至此,问题3被搞定了。


下面搞标量子查询:

由于select 里面还有个标量子查询,是对ps表的,而ps表本身就在left join里面因此 ps 表等价改成:

(select                    product_code,
                           product_sku,
                           price,
                           product_num,
                           COUNT(*) OVER(PARTITION BY product_code) cnt
                      from cy_product_sku
                     where isdel = 0) v_ps

原SQL整体就被等价改为:


SELECT *
  FROM (SELECT v_ps.cnt AS skuNum,
               pc.version,
               pc.product_code,
               pc.product_name,
               v_ps.price,
               pc.gross_weight,
               pc.quantity,
               pc.up_time,
               pb.name AS ppName,
               f_getcategory(pca.id) AS flName,
               v_ps.product_num,
               pt.name AS lxName,
               pc.MAINIMGURL
          FROM cy_product_code pc
          LEFT JOIN cy_product_brand pb
            ON pc.brand_id = pb.id
          LEFT JOIN cy_product_category pca
            ON pca.id = pc.category_id
          LEFT JOIN cy_product_type pt
            ON pc.type_id = pt.id
          LEFT JOIN (select product_code,
                           product_sku,
                           price,
                           product_num,
                           COUNT(*) OVER(PARTITION BY product_code) cnt
                      from cy_product_sku
                     where isdel = 0) v_ps
            ON v_ps.product_code = pc.product_code
         inner join (select product_sku, product_code
                      from (SELECT ROW_NUMBER() OVER(PARTITION BY product_code ORDER BY price, product_sku) ROW_NUM,
                                   product_sku,
                                   product_code
                              FROM cy_product_sku
                             WHERE DEFAULT_SKU IN (2, 3)
                               AND isdel = 0) RS
                     WHERE RS.ROW_NUM = 1) v_tab
            on pc.product_code = v_tab.product_code
           and v_ps.product_sku = v_tab.product_sku
         WHERE pc.isup = 1
           AND pc.isdel = 0
         ORDER BY pc.up_time DESC)
 WHERE rownum <= 10

下面看一下新SQL的执行计划:

--------------------------------------------------------------------------------------------------------
| Id  | Operation                        | Name                | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                 |                     |     1 |  2892 |   205   (2)| 00:00:03 |
|*  1 |  COUNT STOPKEY                   |                     |       |       |            |          |
|   2 |   VIEW                           |                     |     1 |  2892 |   205   (2)| 00:00:03 |
|*  3 |    SORT ORDER BY STOPKEY         |                     |     1 |   483 |   205   (2)| 00:00:03 |
|*  4 |     HASH JOIN OUTER              |                     |     1 |   483 |   204   (1)| 00:00:03 |
|   5 |      NESTED LOOPS OUTER          |                     |     1 |   479 |   200   (1)| 00:00:03 |
|*  6 |       HASH JOIN OUTER            |                     |     1 |   466 |   199   (2)| 00:00:03 |
|*  7 |        HASH JOIN                 |                     |     1 |   450 |   195   (2)| 00:00:03 |
|*  8 |         HASH JOIN                |                     |   807 |   115K|   184   (1)| 00:00:03 |
|*  9 |          VIEW                    |                     |   995 | 37810 |    11  (10)| 00:00:01 |
|* 10 |           WINDOW SORT PUSHED RANK|                     |   995 | 34825 |    11  (10)| 00:00:01 |
|* 11 |            TABLE ACCESS FULL     | CY_PRODUCT_SKU      |   995 | 34825 |    10   (0)| 00:00:01 |
|* 12 |          TABLE ACCESS FULL       | CY_PRODUCT_CODE     |   776 | 84584 |   173   (0)| 00:00:03 |
|  13 |         VIEW                     |                     |  1831 |   541K|    11  (10)| 00:00:01 |
|  14 |          WINDOW SORT             |                     |  1831 | 75071 |    11  (10)| 00:00:01 |
|* 15 |           TABLE ACCESS FULL      | CY_PRODUCT_SKU      |  1831 | 75071 |    10   (0)| 00:00:01 |
|  16 |        TABLE ACCESS FULL         | CY_PRODUCT_TYPE     |    31 |   496 |     4   (0)| 00:00:01 |
|  17 |       TABLE ACCESS BY INDEX ROWID| CY_PRODUCT_BRAND    |     1 |    13 |     1   (0)| 00:00:01 |
|* 18 |        INDEX UNIQUE SCAN         | CY_PRODUCT_BRAND_PK |     1 |       |     0   (0)| 00:00:01 |
|  19 |      TABLE ACCESS FULL           | CY_PRODUCT_CATEGORY |   532 |  2128 |     4   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(ROWNUM<=10)
   3 - filter(ROWNUM<=10)
   4 - access("PCA"."ID"(+)=TO_NUMBER("PC"."CATEGORY_ID"))
   6 - access("PT"."ID"(+)=TO_NUMBER("PC"."TYPE_ID"))
   7 - access("V_PS"."PRODUCT_SKU"="PRODUCT_SKU" AND "V_PS"."PRODUCT_CODE"="PC"."PRODUCT_CODE")
   8 - access("PC"."PRODUCT_CODE"="PRODUCT_CODE")
   9 - filter("RS"."ROW_NUM"=1)
  10 - filter(ROW_NUMBER() OVER ( PARTITION BY "PRODUCT_CODE" ORDER BY
              "PRICE","PRODUCT_SKU")<=1)
  11 - filter(("DEFAULT_SKU"=2 OR "DEFAULT_SKU"=3) AND "ISDEL"=0)
  12 - filter("PC"."ISUP"=1 AND "PC"."ISDEL"=0)
  15 - filter("ISDEL"=0)
  18 - access("PB"."ID"(+)=TO_NUMBER("PC"."BRAND_ID"))

可以看到FILTER和标量子查询部分已经没有了,逻辑读从之前的75万减少到了14万。


下面再优化分页语句解决第二个问题:

首先创建相应的索引:

create index idx_yws on cy_product_code(up_time,isup,isdel);

create index idx_yws2 on CY_PRODUCT_SKU(product_code);

create index idx_yws3 on cy_product_type(id);



从SQL中可以看出pc 表是分页查询的主表,那么就让它和其他表的连接方式走Nested loop 并且让它做驱动表,去驱动其他的副表,优先返回前10行。


SQL 等价改写为:


select *
  from (select *
          from (select rownum rn, a.*
                  from (SELECT /*+ index_desc(pc idx_yws) use_nl(pc,v_ps) use_nl(pc,v_tab) use_nl(pc,pt) use_nl(pc,pca) leading(pc)  */
                         v_ps.cnt AS skuNum,
                         pc.version,
                         pc.product_code,
                         pc.product_name,
                         v_ps.price,
                         pc.gross_weight,
                         pc.quantity,
                         pc.up_time,
                         pb.name AS ppName,
                         f_getcategory(pca.id) AS flName,
                         v_ps.product_num,
                         pt.name AS lxName,
                         pc.MAINIMGURL
                          FROM cy_product_code pc
                          LEFT JOIN cy_product_brand pb
                            ON pc.brand_id = pb.id
                          LEFT JOIN cy_product_category pca
                            ON pca.id = pc.category_id
                          LEFT JOIN cy_product_type pt
                            ON pc.type_id = pt.id
                          LEFT JOIN (select product_code,
                                           product_sku,
                                           price,
                                           product_num,
                                           COUNT(*) OVER(PARTITION BY product_code) cnt
                                      from cy_product_sku
                                     where isdel = 0) v_ps
                            ON v_ps.product_code = pc.product_code
                         inner join (select product_sku, product_code
                                      from (SELECT ROW_NUMBER() OVER(PARTITION BY product_code ORDER BY price, product_sku) ROW_NUM,
                                                   product_sku,
                                                   product_code
                                              FROM cy_product_sku
                                             WHERE DEFAULT_SKU IN (2, 3)
                                               AND isdel = 0) RS
                                     WHERE RS.ROW_NUM = 1) v_tab
                            on pc.product_code = v_tab.product_code
                           and v_ps.product_sku = v_tab.product_sku
                         WHERE pc.isup = 1
                           AND pc.isdel = 0
                         ORDER BY pc.up_time DESC) a)
         where rownum <= 10)
 where rn >= 1


最后的执行计划为:

---------------------------------------------------------------------------------------------------------------
| Id  | Operation                               | Name                | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                        |                     |     1 |  2905 |  8041  (10)| 00:01:37 |
|*  1 |  VIEW                                   |                     |     1 |  2905 |  8041  (10)| 00:01:37 |
|*  2 |   COUNT STOPKEY                         |                     |       |       |            |          |
|   3 |    VIEW                                 |                     |     1 |  2905 |  8041  (10)| 00:01:37 |
|   4 |     COUNT                               |                     |       |       |            |          |
|   5 |      VIEW                               |                     |     1 |  2892 |  8041  (10)| 00:01:37 |
|   6 |       NESTED LOOPS OUTER                |                     |     1 |   541 |  8041  (10)| 00:01:37 |
|   7 |        NESTED LOOPS OUTER               |                     |     1 |   537 |  8037  (10)| 00:01:37 |
|   8 |         NESTED LOOPS OUTER              |                     |     1 |   521 |  8036  (10)| 00:01:37 |
|   9 |          NESTED LOOPS                   |                     |     1 |   508 |  8035  (10)| 00:01:37 |
|  10 |           NESTED LOOPS                  |                     |   807 |   138K|  5613  (14)| 00:01:08 |
|  11 |            TABLE ACCESS BY INDEX ROWID  | CY_PRODUCT_CODE     |   776 | 84584 |  2509   (1)| 00:00:31 |
|* 12 |             INDEX FULL SCAN DESCENDING  | IDX_YWS             |  2709 |       |    14   (0)| 00:00:01 |
|* 13 |            VIEW PUSHED PREDICATE        |                     |     1 |    67 |     4  (25)| 00:00:01 |
|* 14 |             WINDOW SORT PUSHED RANK     |                     |     1 |    35 |     4  (25)| 00:00:01 |
|* 15 |              TABLE ACCESS BY INDEX ROWID| CY_PRODUCT_SKU      |     1 |    35 |     3   (0)| 00:00:01 |
|* 16 |               INDEX RANGE SCAN          | IDX_YWS2            |     7 |       |     1   (0)| 00:00:01 |
|* 17 |           VIEW PUSHED PREDICATE         |                     |     1 |   332 |     3   (0)| 00:00:01 |
|  18 |            WINDOW BUFFER                |                     |     2 |    82 |     3   (0)| 00:00:01 |
|* 19 |             TABLE ACCESS BY INDEX ROWID | CY_PRODUCT_SKU      |     2 |    82 |     3   (0)| 00:00:01 |
|* 20 |              INDEX RANGE SCAN           | IDX_YWS2            |     7 |       |     1   (0)| 00:00:01 |
|  21 |          TABLE ACCESS BY INDEX ROWID    | CY_PRODUCT_BRAND    |     1 |    13 |     1   (0)| 00:00:01 |
|* 22 |           INDEX UNIQUE SCAN             | CY_PRODUCT_BRAND_PK |     1 |       |     0   (0)| 00:00:01 |
|  23 |         TABLE ACCESS BY INDEX ROWID     | CY_PRODUCT_TYPE     |     1 |    16 |     1   (0)| 00:00:01 |
|* 24 |          INDEX RANGE SCAN               | IDX_YWS3            |     1 |       |     0   (0)| 00:00:01 |
|* 25 |        TABLE ACCESS FULL                | CY_PRODUCT_CATEGORY |     1 |     4 |     4   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("RN">=1)
   2 - filter(ROWNUM<=10)
  12 - access("PC"."ISUP"=1 AND "PC"."ISDEL"=0)
       filter("PC"."ISUP"=1 AND "PC"."ISDEL"=0)
  13 - filter("RS"."ROW_NUM"=1)
  14 - filter(ROW_NUMBER() OVER ( PARTITION BY "PRODUCT_CODE" ORDER BY "PRICE","PRODUCT_SKU")<=1)
  15 - filter(("DEFAULT_SKU"=2 OR "DEFAULT_SKU"=3) AND "ISDEL"=0)
  16 - access("PRODUCT_CODE"="PC"."PRODUCT_CODE")
  17 - filter("V_PS"."PRODUCT_SKU"="PRODUCT_SKU")
  19 - filter("ISDEL"=0)
  20 - access("PRODUCT_CODE"="PC"."PRODUCT_CODE")
  22 - access("PB"."ID"(+)=TO_NUMBER("PC"."BRAND_ID"))
  24 - access("PT"."ID"(+)=TO_NUMBER("PC"."TYPE_ID"))
  25 - filter("PCA"."ID"(+)=TO_NUMBER("PC"."CATEGORY_ID"))


最终这个SQL的逻辑读值有203个buffer。




  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值