【问题标题】:Oracle top-n query sort performanceOracle top-n 查询排序性能
【发布时间】:2017-01-01 07:50:29
【问题描述】:

我是 sql 优化的新手,我试图理解为什么 IN 子句中有超过 1 个项目会导致性能受到很大影响,如果可能的话,我该如何防止它。下面或多或少是我正在使用的。第二个查询是我目前拥有的,我正在寻求提高性能。在现实生活中,TABLE_1 有数百万行,计划的排序部分的 CPU 成本为 21M。

SELECT 
    TOPNWRAPPER.*, 
    TABLE_2.X, 
    TABLE_2.Y 
FROM 
    TABLE_2, 
    ( 
        SELECT 
            * 
        FROM 
            ( 
                SELECT 
                    /*+ index (TABLE_1 TABLE_1_B_E_F_ID) */ 
                    TABLE_1.ID, 
                    TABLE_1.C, 
                    TABLE_1.B, 
                    TABLE_1.E, 
                    TABLE_1.F
                FROM 
                    TABLE_1 
                WHERE 
                    ( TABLE_1.F IN ( ‘STATE1’ ) ) AND 
                    ( TABLE_1.B= 'SOMETEXT' ) AND 
                    ( TABLE_1.C=1 ) AND 
                    ( TABLE_1.E= 'IN' ) AND 
                    ( TABLE_1.D IS NULL ) 
                ORDER BY 
                    TABLE_1.ID 
            ) 
        WHERE 
            ( ROWNUM <= 150 ) 
    ) TOPNWRAPPER 
WHERE 
    ( TOPNWRAPPER.ID = TABLE_2.T1_ID_FK ) 
ORDER BY 
    TOPNWRAPPER.ID ASC

统计:

|--------------------------------------------------------------------------------------------------------------------------|
|| Id  | Operation                        | Name                        | Starts | E-Rows | A-Rows |   A-Time   | Buffers ||
|--------------------------------------------------------------------------------------------------------------------------|
||   0 ||SELECT STATEMENT                 |                             |      1 |        |    120 |00:00:00.01 |     965 ||
||   1 |||NESTED LOOPS                    |                             |      1 |        |    120 |00:00:00.01 |     965 ||
||   2 ||||NESTED LOOPS                   |                             |      1 |      1 |    120 |00:00:00.01 |     845 ||
||   3 |||||VIEW                          |                             |      1 |      1 |    120 |00:00:00.01 |     245 ||
||*  4 ||||||COUNT STOPKEY                |                             |      1 |        |    120 |00:00:00.01 |     245 ||
||   5 |||||||VIEW                        |                             |      1 |      1 |    120 |00:00:00.01 |     245 ||
||*  6 ||||||||TABLE ACCESS BY INDEX ROWID| TABLE_1                     |      1 |      1 |    120 |00:00:00.01 |     245 ||
||*  7 |||||||||INDEX RANGE SCAN          | TABLE_1_B_E_F_ID            |      1 |     25 |    120 |00:00:00.01 |     125 ||
||*  8 |||||INDEX RANGE SCAN              | TABLE_2_T1_ID_FK            |    120 |      1 |    120 |00:00:00.01 |     600 ||
||   9 ||||TABLE ACCESS BY INDEX ROWID    | TABLE_2                     |    120 |      1 |    120 |00:00:00.01 |     120 ||
|--------------------------------------------------------------------------------------------------------------------------|
|                                                                                                                          |
|Predicate Information (identified by operation id):                                                                       |
|---------------------------------------------------                                                                       |
|                                                                                                                          |
|   4 - filter(ROWNUM<=150)                                                                                                |
|   6 - filter((“TABLE_1”.”C”=1 AND “TABLE_1”.”D” IS NULL))                                                                |
|   7 - access(“TABLE_1”.”B”='SOMETEXT' AND                                                                                |
|              “TABLE_1”.”E”=‘IN' AND “TABLE_1”.”F”=’STATE1’)                                                              |
|   8 - access(“TOPNWRAPPER”.”ID”=“TABLE_2”.”T1_ID_FK”)                                                                         |
+--------------------------------------------------------------------------------------------------------------------------+

当我将查询更新为在 IN 子句中包含“STATE2”时,会向计划中添加一个额外的排序步骤。

SELECT 
    TOPNWRAPPER.*, 
    TABLE_2.X, 
    TABLE_2.Y 
FROM 
    TABLE_2, 
    ( 
        SELECT 
            * 
        FROM 
            ( 
                SELECT 
                    /*+ index (TABLE_1 TABLE_1_B_E_F_ID) */ 
                    TABLE_1.ID, 
                    TABLE_1.C, 
                    TABLE_1.B, 
                    TABLE_1.E, 
                    TABLE_1.F
                FROM 
                    TABLE_1 
                WHERE 
                    ( TABLE_1.F IN ( 'STATE1', 'STATE2' ) ) AND 
                    ( TABLE_1.B= 'SOMETEXT' ) AND 
                    ( TABLE_1.C=1 ) AND 
                    ( TABLE_1.E= 'IN' ) AND 
                    ( TABLE_1.D IS NULL ) 
                ORDER BY 
                    TABLE_1.ID 
            ) 
        WHERE 
            ( ROWNUM <= 150 ) 
    ) TOPNWRAPPER 
WHERE 
    ( TOPNWRAPPER.ID = TABLE_2.T1_ID_FK ) 
ORDER BY 
    TOPNWRAPPER.ID ASC

统计:

|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|| Id  | Operation                          | Name                        | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem ||
|-------------------------------------------------------------------------------------------------------------------------------------------------------|
||   0 ||SELECT STATEMENT                   |                             |      1 |        |    150 |00:00:00.01 |    1076 |       |       |          ||
||   1 |||NESTED LOOPS                      |                             |      1 |        |    150 |00:00:00.01 |    1076 |       |       |          ||
||   2 ||||NESTED LOOPS                     |                             |      1 |      1 |    150 |00:00:00.01 |     926 |       |       |          ||
||   3 |||||VIEW                            |                             |      1 |      1 |    150 |00:00:00.01 |     176 |       |       |          ||
||*  4 ||||||COUNT STOPKEY                  |                             |      1 |        |    150 |00:00:00.01 |     176 |       |       |          ||
||   5 |||||||VIEW                          |                             |      1 |      1 |    150 |00:00:00.01 |     176 |       |       |          ||
||*  6 ||||||||SORT ORDER BY STOPKEY        |                             |      1 |      1 |    150 |00:00:00.01 |     176 | 15360 | 15360 |14336  (0)||
||   7 |||||||||INLIST ITERATOR             |                             |      1 |        |    165 |00:00:00.01 |     176 |       |       |          ||
||*  8 ||||||||||TABLE ACCESS BY INDEX ROWID| TABLE_1                     |      2 |      1 |    165 |00:00:00.01 |     176 |       |       |          ||
||*  9 |||||||||||INDEX RANGE SCAN          | TABLE_1_B_E_F_ID            |      2 |     50 |    165 |00:00:00.01 |      11 |       |       |          ||
||* 10 |||||INDEX RANGE SCAN                | TABLE_2_T1_ID_FK            |    150 |      1 |    150 |00:00:00.01 |     750 |       |       |          ||
||  11 ||||TABLE ACCESS BY INDEX ROWID      | TABLE_2                     |    150 |      1 |    150 |00:00:00.01 |     150 |       |       |          ||
|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|                                                                                                                                                       |
|Predicate Information (identified by operation id):                                                                                                    |
|---------------------------------------------------                                                                                                    |
|                                                                                                                                                       |
|   4 - filter(ROWNUM<=150)                                                                                                                             |
|   6 - filter(ROWNUM<=150)                                                                                                                             |
|   8 - filter((“TABLE_1”.”C”=1 AND “TABLE_1”.”D” IS NULL))                                         |
|   9 - access(“TABLE_1”.”B”='SOMETEXT' AND                                                                                |
|              “TABLE_1”.”E”='IN' AND ((“TABLE_1”.”F”='STATE1') OR (“TABLE_1”.”F”='STATE2'))                                              |
|  10 - access(“TOPNWRAPPER”.”ID”=“TABLE_2”.”T1_ID_FK”)                                                              |
|                                                                                                                                                       |
+-------------------------------------------------------------------------------------------------------------------------------------------------------+

我已经环顾了几天了。我尝试的一个建议是使用提示 /*+ USE_CONCAT (OR_PREDICATES(1)) */,这有助于将内存使用量减少一半,但并没有完全消除问题。

编辑:环顾四周 (http://use-the-index-luke.com/sql/sorting-grouping/indexed-order-by#tip-ixord-full) 并认为这可能是由于订单。如果我将语句的顺序更改为:TABLE_1.F,TABLE_1.IDTOPNWRAPPER.F,TOPNWRAPPER.ID ASC,那么排序操作就会消失,不幸的是我需要基于 ID 的前 n 行。或者,我尝试在 (ID F) 上创建一个新索引以进行测试,它也确实删除了排序操作,但 ID 每行都是唯一的,这使得表访问操作的效率降低。

编辑 2:

OPERATION      |OPTION           |CPU COST
--------------------------------------------
SORT           |ORDER BY STOPKEY |21042774
|NESTED LOOPS  |OUTER            |56052
||TABLE ACCESS |BY INDEX ROWID   |38980
|||INDEX       |RANGE SCAN       |30086

【问题讨论】:

  • 您的查询没有TOPNWRAPPER.ID
  • 谢谢,这是重命名超长表名和列时的疏忽。我想我现在已经纠正了。
  • 查询计划是什么? STATE1 谓词的选择性如何? STATE2 谓词的选择性如何?每种情况下内部查询返回多少行需要排序?
  • 根据查询计划,这两个查询的运行时间都不到 0.01 秒。您希望第二个查询运行多快?
  • 使用common table expressions而不是嵌套子查询将使您的代码更具可读性。

标签: sql oracle query-optimization oracle12c


【解决方案1】:

使用EXISTS 而不是IN

例子:

EXISTS (select 1 from DUAL where TABLE_1.F='STATE1' or TABLE_1.F='STATE2')

试试看计划有没有改变。

如果您想使用NOT IN,请使用提示HASH_AJNL_AJ

【讨论】:

    【解决方案2】:

    性能差异可能无关紧要。执行计划的不同是因为多列索引访问只有在前导列使用相等条件时才会隐式排序。

    性能差异

    不要太担心执行计划的成本。尽管它被称为“基于成本的优化器”,但成本是一个奇怪的数字,世界上只有少数人完全理解。

    解释计划成本比较复杂的一个原因是总成本有时小于子运营成本之一。正如我在my answer here 中解释的那样,COUNT STOPKEY 操作可能会发生这种情况。这是甲骨文的说法“这个子操作花费这么多,但 COUNT STOPKEY 可能会在它变得那么高之前将其切断”。通常最好比较计划的最高成本,但即使是这个数字有时也会产生误导,正如该答案中的其他示例所表明的那样。

    这意味着通常运行时间是唯一重要的事情。如果两个查询的 A-Time(实际时间)仅为 0.1 秒,那么您的工作可能已经完成。

    执行计划差异

    执行计划的差异是由多列索引的存储和访问方式造成的。有时当扫描索引时,结果会自动存储,有时则不会。这就是为什么一个计划有COUNT STOPKEY 而另一个计划有更昂贵的SORT ORDER BY STOPKEY

    为了演示这种计划差异,创建一个只有 2 列和 4 行的简单表和索引:

    create table test1 as 
    select 1 a, 10 b from dual union all
    select 1 a, 30 b from dual union all
    select 2 a, 20 b from dual union all
    select 2 a, 40 b from dual;
    
    create index test1_idx on test1(a, b);
    
    begin
        dbms_stats.gather_table_stats(user, 'TEST1');
    end;
    /
    

    以下是如何存储索引的简化概念。数据先按前导列排序,再按尾随列排序。

                   +----+
            +------+Root+-------+
            |      +----+       |
            |                   |
          +-v-+               +-v-+
       +--+A=1+--+         +--+A=2+--+
       |  +---+  |         |  +---+  |
       |         |         |         |
     +-v--+   +--v-+     +-v--+   +--v-+
     |B=10|   |B=30|     |B=20|   |B=40|
     +----+   +----+     +----+   +----+
    

    如果查询仅访问前导列 A 中的 一个 值,则它可以按顺序读取列 B 中的值,而无需任何额外的努力。 Oracle 去 A 块之一,然后按顺序读取 B 块,甚至没有尝试。

    请注意此查询如何具有ORDER BY,但执行计划中没有SORT

    explain plan for select * from test1 where a = 1 and b > 0 order by b;
    select * from table(dbms_xplan.display(format => 'basic'));
    
    Plan hash value: 598212486
    
    --------------------------------------
    | Id  | Operation        | Name      |
    --------------------------------------
    |   0 | SELECT STATEMENT |           |
    |   1 |  INDEX RANGE SCAN| TEST1_IDX |
    --------------------------------------
    

    但如果查询访问前导列A中的多个值,则B的结果不一定会按顺序检索。 Oracle 可能按顺序读取 A 块,但 B 块顺序仅对 一个 A 值有效。

    现在,执行计划中出现了一个额外的SORT ORDER BY 操作。

    explain plan for select * from test1 where a in (1,2) and b > 0 order by b;
    select * from table(dbms_xplan.display(format => 'basic'));
    
    Plan hash value: 704117715
    
    ----------------------------------------
    | Id  | Operation          | Name      |
    ----------------------------------------
    |   0 | SELECT STATEMENT   |           |
    |   1 |  SORT ORDER BY     |           |
    |   2 |   INLIST ITERATOR  |           |
    |   3 |    INDEX RANGE SCAN| TEST1_IDX |
    ----------------------------------------
    

    这就是为什么将column1 = value1 更改为column1 in (value1, value2) 可能会添加额外的SORT 操作。

    【讨论】:

    • 谢谢,这说明了很多。
    • 快速跟进。我遇到的问题与您链接到的问题相反,因为父操作明显大于其子操作。只要执行时间很短,这些信息是否仍然无关紧要?降低 CPU 成本有什么好处?数据看起来像这样,最右边的数字是 CPU 成本。 [在此处未包含块后添加为我的帖子的编辑]
    • @Lex 是的,只要执行时间短,这就是一切。 (想到的唯一例外是并行查询,在该问题上投入更多线程可能会使其运行得更快,但可能会浪费大量资源。但这与这里无关。)父操作应该大于子操作操作,它们应该是子操作的总和。您可能想比较两个计划的 ID 0 成本,这通常是“总”成本。如果不转化为运行时间的真正收益,那么降低成本就没有任何好处。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多