增量 "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 |||||||||||||||||||||||||||||||||||||||||||||||||
我有一个如下所示的查询:
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 |||||||||||||||||||||||||||||||||||||||||||||||||