增量 "bigger than" 性能与单独 "between" 运算符的查询性能

Query Performance of incremental "bigger than"performance vs. separate "between" operator

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

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;

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

好吧,让我做一个 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 的查询中会获得比其他查询更好的性能。

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

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

我想 between 版本可以让您将最常出现的值放在第一位,理论上,如果大部分值落在一两个 运行ges 范围内,则可以提高效率。再一次,< 版本只需要对每个案例进行一次比较,而不是两次。

我仍然奖励 < 版本更多的风格点:)

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

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 |||||||||||||||||||||||||||||||||||||||||||||||||