【问题标题】:Query Performance of incremental "bigger than"performance vs. separate "between" operator增量“大于”性能与单独“之间”运算符的查询性能
【发布时间】:2021-12-09 00:17:15
【问题描述】:

我有一个查询,如下所示:

select         
CASE
WHEN col BETWEEN 0 AND 20 THEN 0
WHEN col BETWEEN 20 AND 50 THEN 20
WHEN col BETWEEN 50 AND 100 THEN 40
WHEN col BETWEEN 100 AND 200 THEN 75
WHEN col BETWEEN 200 AND 10000 THEN 86
ELSE 0 END AS t_col
from TAB1;

我想获得更好的性能并将其重写为:

select case  
WHEN col < 20 then 0
WHEN col < 50 then 20
WHEN col < 100 then 40
WHEN col < 200 then 75
WHEN col < 1000 then 86
END AS t_col
from TAB1;

我认为第二个查询可能会更快,因为不会创建间隔,而只会将列的值与一个数字进行比较。解释计划给我两个查询相同的结果。我想知道他们中哪一个的表现更好?

【问题讨论】:

  • BETWEEN 包括两个边界,因此您可能希望在第二个查询中使用&lt;=。至于性能:同时运行它们并亲自查看。在小型数据集上,您不会注意到任何差异。那张桌子有多大?
  • 所有这些都是由 CPU 在 RAM 中完成的,差异可能在纳秒范围内。不要在这个“问题”上浪费时间。
  • 执行计划是相同的,因为它不关心 SELECT 列表,除非它包含标量子查询。
  • 我想between 版本可以让您将最频繁出现的值放在首位,理论上可以节省一些 CPU 周期。再说一次,第二个版本只需要每个案例进行一次比较,而不是两次。但正如@WernfriedDomscheit 所说,它不太可能对任何现实世界的查询产生可衡量的差异。

标签: sql database oracle query-optimization


【解决方案1】:

它们实际上是相同的。 CPU 周期的任何微小差异都会被执行查询所需的 I/O、网络开销等所掩盖。

我想between 版本可以让您将最频繁出现的值放在首位,理论上如果大部分值落在一个或两个范围内,则效率会提高。再说一次,&lt; 版本每个案例只需要一次比较,而不是两次。

我还是给&lt;版加分吧:)

我在 PL/SQL 游标循环中测试了这两个版本以消除网络问题,并多次运行测试。结果在超过 1000 万次迭代中彼此相差半秒之内,这与多次运行的变化大致相同(因为在任何服务器或笔记本电脑上总是在运行其他东西)。 p>

create table t1 (c1, c2, c3) pctfree 0 nologging
as
select round(dbms_random.value(1,100))
     , cast(dbms_random.string('X',40) as varchar2(40))
     , cast(dbms_random.string('X',40) as varchar2(40))
from   xmltable('1 to 1000000');
declare
    type test_rectype is record(num number);

    totalTime1 simple_integer := 0;
    totalTime2 simple_integer := 0;

    time1Percentage simple_integer := 0;
    time2Percentage simple_integer := 0;

    function testCursor
        ( cursorNum in integer )
        return sys_refcursor
    is
        testCursor sys_refcursor;
    begin
        if cursorNum = 1 then
            open testCursor for
                select case
                           when c1 < 20 then 0
                           when c1 < 50 then 20
                           when c1 < 100 then 40
                           when c1 < 200 then 75
                           when c1 < 1000 then 86
                       end as t_c1
                from   t1;
        elsif cursorNum = 2 then
            open testCursor for
               select case
                           when c1 < 20 then 0
                           when c1 < 50 then 20
                           when c1 < 100 then 40
                           when c1 < 200 then 75
                           when c1 < 1000 then 86
                       end as t_c1
                from   t1;
        end if;

        return testCursor;
    end testCursor;
      
    -- Fetch all rows from a cursor and return time in hundredths of a second:
    procedure time_cursor
        ( inCursor in sys_refcursor
        , outTime  in out nocopy simple_integer )
    is
        startTime simple_integer := dbms_utility.get_time;
    begin
        -- 21c new iterator syntax
        for r test_rectype in values of inCursor loop
            null;  -- Could also compare rowcounts here
        end loop;

        outTime := dbms_utility.get_time - startTime;

        close inCursor;
    end time_cursor;

    -- Report timing difference:
    procedure print_comparison
        ( time1 simple_integer
        , time2 simple_integer )
    is
    begin
        time1Percentage := 100 * time1 / (time1 + time2);
        time2Percentage := 100 * time2 / (time2 + time2);

        dbms_output.put_line('Between:  '||to_char(time1/100,'900d00')|| rpad(' |',time1Percentage,'|'));
        dbms_output.put_line('LessThan: '||to_char(time2/100,'900d00')|| rpad(' |',time2Percentage,'|'));
    end print_comparison;

    procedure compare_cursors
        ( runningTime1 in out nocopy simple_integer
        , runningTime2 in out nocopy simple_integer )
    is
        testCursor1 sys_refcursor := testCursor(1);
        testCursor2 sys_refcursor := testCursor(2);

        time1 simple_integer := 0;
        time2 simple_integer := 0;

        time1Percentage simple_integer := 0;
        time2Percentage simple_integer := 0;
    begin
        time_cursor(testCursor1, time1);
        time_cursor(testCursor2, time2);

        print_comparison(time1, time2);

        -- Update running totals:
        runningTime1 := nvl(runningTime1,0) + nvl(time1,0);
        runningTime2 := nvl(runningTime2,0) + nvl(time2,0);

        dbms_output.new_line;
    end compare_cursors;
begin
    for i in 1..10 loop
        compare_cursors(totalTime1, totalTime2);
    end loop;

    dbms_output.put_line('Total:'||chr(10));

    print_comparison(totalTime1, totalTime2);
end;
Between:    02.18 ||||||||||||||||||||||||||||||||||||||||||||||||
LessThan:   02.25 |||||||||||||||||||||||||||||||||||||||||||||||||

Between:    02.03 |||||||||||||||||||||||||||||||||||||||||||||||||
LessThan:   02.03 |||||||||||||||||||||||||||||||||||||||||||||||||

Between:    02.13 |||||||||||||||||||||||||||||||||||||||||||||||||
LessThan:   02.13 |||||||||||||||||||||||||||||||||||||||||||||||||

Between:    02.13 ||||||||||||||||||||||||||||||||||||||||||||||||||
LessThan:   02.07 |||||||||||||||||||||||||||||||||||||||||||||||||

Between:    02.16 ||||||||||||||||||||||||||||||||||||||||||||||||
LessThan:   02.27 |||||||||||||||||||||||||||||||||||||||||||||||||

Between:    02.28 |||||||||||||||||||||||||||||||||||||||||||||||||
LessThan:   02.28 |||||||||||||||||||||||||||||||||||||||||||||||||

Between:    02.17 ||||||||||||||||||||||||||||||||||||||||||||||||
LessThan:   02.27 |||||||||||||||||||||||||||||||||||||||||||||||||

Between:    02.24 ||||||||||||||||||||||||||||||||||||||||||||||||
LessThan:   02.30 |||||||||||||||||||||||||||||||||||||||||||||||||

Between:    02.24 |||||||||||||||||||||||||||||||||||||||||||||||||
LessThan:   02.27 |||||||||||||||||||||||||||||||||||||||||||||||||

Between:    02.25 ||||||||||||||||||||||||||||||||||||||||||||||||||
LessThan:   02.18 |||||||||||||||||||||||||||||||||||||||||||||||||

Total:

Between:    21.81 |||||||||||||||||||||||||||||||||||||||||||||||||
LessThan:   22.05 |||||||||||||||||||||||||||||||||||||||||||||||||

【讨论】:

    【解决方案2】:

    好吧,让我做一个 PoC 看看会发生什么

    SQL> create table t1 ( c1 number, c2 varchar2(40) , c3 varchar2(40) ) ;
    
    Table created.
    
    SQL> declare
      2  begin
      3  for i in 1 .. 1000000
      4  loop
      5   insert into t1 values ( round(dbms_random.value(1,100)) , dbms_random.string('X',40) , dbms_random.string('X',40) );
      6  end loop;
      7  end;
      8  /
    
    PL/SQL procedure successfully completed.
    
    SQL> exec dbms_stats.gather_table_stats('TESTUSER','T1');
    
    PL/SQL procedure successfully completed.
    
    SQL> select count(*) from t1 ;
    
      COUNT(*)
    ----------
       1000000
    

    场景

    SQL> set autotrace traceonly
    SQL> select
    CASE
    WHEN c1 BETWEEN 0 AND 20 THEN 0
    WHEN c1 BETWEEN 20 AND 50 THEN 20
    WHEN c1 BETWEEN 50 AND 100 THEN 40
    WHEN c1 BETWEEN 100 AND 200 THEN 75
    WHEN c1 BETWEEN 200 AND 10000 THEN 86
    ELSE 0 END AS t_c1
    from t1;  2    3    4    5    6    7    8    9
    
    1000000 rows selected.
    
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 3617692013
    
    --------------------------------------------------------------------------
    | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
    --------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |      |  1000K|  2929K|  2322   (1)| 00:00:01 |
    |   1 |  TABLE ACCESS FULL| T1   |  1000K|  2929K|  2322   (1)| 00:00:01 |
    --------------------------------------------------------------------------
    
    SQL> select case
    WHEN c1 < 20 then 0
    WHEN c1 < 50 then 20
    WHEN c1 < 100 then 40
    WHEN c1 < 200 then 75
    WHEN c1 < 1000 then 86
    END AS t_c1
    from t1;  2    3    4    5    6    7    8
    
    1000000 rows selected.
    
    
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 3617692013
    
    --------------------------------------------------------------------------
    | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
    --------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |      |  1000K|  2929K|  2322   (1)| 00:00:01 |
    |   1 |  TABLE ACCESS FULL| T1   |  1000K|  2929K|  2322   (1)| 00:00:01 |
    --------------------------------------------------------------------------
    

    它们的行为相同,对吧?不完全是,虽然计划看起来一样,但让我们检查一下统计数据。为此,我将在每次测试后刷新缓冲区缓存和共享池。

    SQL> set autotrace traceonly timing on
    SQL> select
    CASE
    WHEN c1 BETWEEN 0 AND 20 THEN 0
    WHEN c1 BETWEEN 20 AND 50 THEN 20
    WHEN c1 BETWEEN 50 AND 100 THEN 40
    WHEN c1 BETWEEN 100 AND 200 THEN 75
    WHEN c1 BETWEEN 200 AND 10000 THEN 86
    ELSE 0 END AS t_c1
    from t1;  2    3    4    5    6    7    8    9
    
    1000000 rows selected.
    
    Elapsed: 00:00:02.92
    
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 3617692013
    
    --------------------------------------------------------------------------
    | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
    --------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |      |  1000K|  2929K|  2322   (1)| 00:00:01 |
    |   1 |  TABLE ACCESS FULL| T1   |  1000K|  2929K|  2322   (1)| 00:00:01 |
    --------------------------------------------------------------------------
    
    
    Statistics
    ----------------------------------------------------------
             12  recursive calls
              0  db block gets
          72870  consistent gets
           6180  physical reads
              0  redo size
       19435128  bytes sent via SQL*Net to client
         733901  bytes received via SQL*Net from client
          66668  SQL*Net roundtrips to/from client
              3  sorts (memory)
              0  sorts (disk)
        1000000  rows processed
    
    SQL> alter system flush shared_pool ;
    
    System altered.
    
    Elapsed: 00:00:00.08
    SQL> alter system flush buffer_cache ;
    
    System altered.
    
    Elapsed: 00:00:00.04
    SQL> select t1.* , case
    WHEN c1 < 20 then 0
    WHEN c1 < 50 then 20
    WHEN c1 < 100 then 40
    WHEN c1 < 200 then 75
    WHEN c1 < 1000 then 86
    END AS t_c1
    from t1;  2    3    4    5    6    7    8
    
    1000000 rows selected.
    
    Elapsed: 00:00:03.49
    
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 3617692013
    
    --------------------------------------------------------------------------
    | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
    --------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |      |  1000K|    81M|  2323   (1)| 00:00:01 |
    |   1 |  TABLE ACCESS FULL| T1   |  1000K|    81M|  2323   (1)| 00:00:01 |
    --------------------------------------------------------------------------
    
    
    Statistics
    ----------------------------------------------------------
             18  recursive calls
              0  db block gets
          72878  consistent gets
           6180  physical reads
              0  redo size
      101747627  bytes sent via SQL*Net to client
         733834  bytes received via SQL*Net from client
          66668  SQL*Net roundtrips to/from client
              3  sorts (memory)
              0  sorts (disk)
        1000000  rows processed
    

    第二个比第一个慢,可能是因为recursive calls 在第二个 (18) 中针对第一个 (12) 完成。如果您开始放置更多字段,并且数据量很大,我很确定您在使用between 的查询中会获得比在其他查询中更好的性能。

    但显然,这只是一种感觉,你必须在自己的数据库中测试自己。我很确定最终查询包含其他字段,where 条件等......因此,此答案仅涵盖您在原始问题中输入的内容,而不是在具有许多其他字段的真实案例场景中会发生的情况, where 条件,索引等...

    【讨论】:

    • 这是一个非常准确的答案。我有几个比较字段,并且视图很大。所以这些秒数可能长达几分钟。
    • 谢谢@Amir,很高兴它有帮助:)
    • 额外的递归调用可能与解析有关,因为重新运行时数字变为 0,在这种情况下,语法的选择不会对实际查询产生可衡量的影响。当我运行测试时,最大的因素是 SQL*Plus 数组大小设置,在重复运行时,唯一不同的自动跟踪统计数据是通过 SQL*Net 发送/接收的字节数,而时间变化最多第二个可能是由于系统中正在进行的其他处理。
    • 这很清楚。一旦解析脱离等式。在我的回答场景中,当时系统中没有运行任何东西,而是测试本身。我做了几个,第一个总是比第二个快一点。不过,我没有使用 10046 跟踪事件来查看它背后真正发生的事情。无论如何,正如我所说,这只是一种感觉,如果不处理真正的查询,可能不值得深入探讨。我同意 William 的观点,在真实场景中,调用变得无关紧要,因为软解析会解决这个问题。
    • 我刚刚注意到第二个版本(带有&lt;)包含t1.*,而带有between 的版本没有。这可能会导致额外的解析调用和更长的运行时间,因为要获取的数据更多。
    猜你喜欢
    • 2013-02-05
    • 1970-01-01
    • 1970-01-01
    • 2011-03-13
    • 1970-01-01
    • 2019-01-24
    • 2013-05-28
    • 1970-01-01
    • 2016-01-24
    相关资源
    最近更新 更多