为了备份 JW 和 a_horse_with_no_name 的语句,这里是一个测试,都使用相同的连接,因此这里忽略连接(池)问题。
首先使用主键创建表并为优化器收集统计信息:
SQL> create table mytable
2 as
3 select level id
4 , lpad('*',1000,'*') filler
5 from dual
6 connect by level <= 10000
7 /
Table created.
SQL> alter table mytable add constraint my_pk primary key (id)
2 /
Table altered.
SQL> exec dbms_stats.gather_table_stats(user,'mytable')
PL/SQL procedure successfully completed.
接下来,将所有语句放在共享池中,作为热身,切断大部分查询的输出:
SQL> select * from mytable where id = 1
2 /
ID
----------
FILLER
---------------------------------------------------------------------------------------------------------------------------------------
1
***************************************************************************************************************************************
***************************************************************************************************************************************
***************************************************************************************************************************************
***************************************************************************************************************************************
***************************************************************************************************************************************
***************************************************************************************************************************************
***************************************************************************************************************************************
*******************************************************
1 row selected.
SQL> select * from mytable where id = 2
2 /
1 row selected.
SQL> select * from mytable where id = 3
2 /
1 row selected.
SQL> select * from mytable where id = 4
2 /
1 row selected.
SQL> select * from mytable where id in (1,2,3,4)
2 /
4 rows selected.
接下来,检查自动跟踪输出,以查看数据库必须执行的工作:
SQL> set autotrace traceonly
SQL> select * from mytable where id = 1
2 /
1 row selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3280897506
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1005 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| MYTABLE | 1 | 1005 | 2 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | MY_PK | 1 | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ID"=1)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
1387 bytes sent via SQL*Net to client
448 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL> select * from mytable where id = 2
2 /
1 row selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3280897506
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1005 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| MYTABLE | 1 | 1005 | 2 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | MY_PK | 1 | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ID"=2)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
1387 bytes sent via SQL*Net to client
448 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL> select * from mytable where id = 3
2 /
1 row selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3280897506
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1005 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| MYTABLE | 1 | 1005 | 2 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | MY_PK | 1 | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ID"=3)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
1387 bytes sent via SQL*Net to client
448 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL> select * from mytable where id = 4
2 /
1 row selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3280897506
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 1005 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| MYTABLE | 1 | 1005 | 2 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | MY_PK | 1 | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ID"=4)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
1387 bytes sent via SQL*Net to client
448 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL> select * from mytable where id in (1,2,3,4)
2 /
4 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 1637292604
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 | 4020 | 4 (0)| 00:00:01 |
| 1 | INLIST ITERATOR | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID| MYTABLE | 4 | 4020 | 4 (0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN | MY_PK | 4 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("ID"=1 OR "ID"=2 OR "ID"=3 OR "ID"=4)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
2435 bytes sent via SQL*Net to client
448 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4 rows processed
SQL> set autotrace off
四次 3 一致得到对 8 一致得到。最后,让我们通过执行所有语句 10,000 次来为这两种变体计时:
SQL> set timing on
SQL> declare
2 v_id number;
3 v_filler varchar2(1000);
4 begin
5 for i in 1 .. 10000
6 loop
7 select *
8 into v_id, v_filler
9 from mytable
10 where id = 1
11 ;
12 select *
13 into v_id, v_filler
14 from mytable
15 where id = 2
16 ;
17 select *
18 into v_id, v_filler
19 from mytable
20 where id = 3
21 ;
22 select *
23 into v_id, v_filler
24 from mytable
25 where id = 4
26 ;
27 end loop;
28 end;
29 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:01.03
SQL> /
PL/SQL procedure successfully completed.
Elapsed: 00:00:01.00
SQL> /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.99
SQL> declare
2 v_id number;
3 v_filler varchar2(1000);
4 begin
5 for i in 1 .. 10000
6 loop
7 for r in (select * from mytable where id in (1,2,3,4))
8 loop
9 v_id := r.id;
10 v_filler := r.filler;
11 end loop;
12 end loop;
13 end;
14 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.41
SQL> /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.39
SQL> /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.39
SQL> set timing off
在这里,您可以看到向数据库发送四倍多的语句的开销最多。根据索引深度和聚类因子,这里和那里可能存在一些变化,但一个查询与四个查询仍然是最大的区别。
所以,希望这个线程的读者不会再说“已知 IN 子句很昂贵”:-)
问候,
罗布。