To backup JW's and a_horse_with_no_name's statements, here is a test, all using the same connection, so connection (pooling) issues are disregarded here.
First create the table with a primary key and gather statistics for the optimizer:
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.
Next, place all statements in the shared pool, as a warming-up, cutting most of the output of the queries:
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.
Next, examine the autotrace output, to see the work the database has to do:
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
Four times 3 consistent gets versus 8 consistent gets.
Finally, let's time both variants by executing all statements 10,000 times:
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
Here you can see the overhead of sending four times more statements towards the database counts the most. There is some variation possible here and there, depending on index depth and clustering factor, but the one query versus four queries will remain the biggest difference.
So, hopefully, readers of this thread won't say "IN clauses are known to be expensive" anymore :-)
Regards,
Rob.