I'm new to sql optimization and I'm trying to understand why having more than 1 item in the IN clause results in large performance hit and how can I prevent it if it is even possible. Below is more or less what I'm working with. The second query is currently what I have and I'm looking to improve the performance. In real life TABLE_1 has millions of rows and the sorting part of the plan has a CPU cost of 21M.
SELECT
TOPNWRAPPER.*,
TABLE_2.X,
TABLE_2.Y
FROM
TABLE_2,
(
SELECT
*
FROM
(
SELECT
/*+ index (TABLE_1 TABLE_1_B_E_F_ID) */
TABLE_1.ID,
TABLE_1.C,
TABLE_1.B,
TABLE_1.E,
TABLE_1.F
FROM
TABLE_1
WHERE
( TABLE_1.F IN ( ‘STATE1’ ) ) AND
( TABLE_1.B= 'SOMETEXT' ) AND
( TABLE_1.C=1 ) AND
( TABLE_1.E= 'IN' ) AND
( TABLE_1.D IS NULL )
ORDER BY
TABLE_1.ID
)
WHERE
( ROWNUM <= 150 )
) TOPNWRAPPER
WHERE
( TOPNWRAPPER.ID = TABLE_2.T1_ID_FK )
ORDER BY
TOPNWRAPPER.ID ASC
Stats:
|--------------------------------------------------------------------------------------------------------------------------|
|| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers ||
|--------------------------------------------------------------------------------------------------------------------------|
|| 0 ||SELECT STATEMENT | | 1 | | 120 |00:00:00.01 | 965 ||
|| 1 |||NESTED LOOPS | | 1 | | 120 |00:00:00.01 | 965 ||
|| 2 ||||NESTED LOOPS | | 1 | 1 | 120 |00:00:00.01 | 845 ||
|| 3 |||||VIEW | | 1 | 1 | 120 |00:00:00.01 | 245 ||
||* 4 ||||||COUNT STOPKEY | | 1 | | 120 |00:00:00.01 | 245 ||
|| 5 |||||||VIEW | | 1 | 1 | 120 |00:00:00.01 | 245 ||
||* 6 ||||||||TABLE ACCESS BY INDEX ROWID| TABLE_1 | 1 | 1 | 120 |00:00:00.01 | 245 ||
||* 7 |||||||||INDEX RANGE SCAN | TABLE_1_B_E_F_ID | 1 | 25 | 120 |00:00:00.01 | 125 ||
||* 8 |||||INDEX RANGE SCAN | TABLE_2_T1_ID_FK | 120 | 1 | 120 |00:00:00.01 | 600 ||
|| 9 ||||TABLE ACCESS BY INDEX ROWID | TABLE_2 | 120 | 1 | 120 |00:00:00.01 | 120 ||
|--------------------------------------------------------------------------------------------------------------------------|
| |
|Predicate Information (identified by operation id): |
|--------------------------------------------------- |
| |
| 4 - filter(ROWNUM<=150) |
| 6 - filter((“TABLE_1”.”C”=1 AND “TABLE_1”.”D” IS NULL)) |
| 7 - access(“TABLE_1”.”B”='SOMETEXT' AND |
| “TABLE_1”.”E”=‘IN' AND “TABLE_1”.”F”=’STATE1’) |
| 8 - access(“TOPNWRAPPER”.”ID”=“TABLE_2”.”T1_ID_FK”) |
+--------------------------------------------------------------------------------------------------------------------------+
When I update the query to have 'STATE2' in the IN clause an additional sort step is added to the plan.
SELECT
TOPNWRAPPER.*,
TABLE_2.X,
TABLE_2.Y
FROM
TABLE_2,
(
SELECT
*
FROM
(
SELECT
/*+ index (TABLE_1 TABLE_1_B_E_F_ID) */
TABLE_1.ID,
TABLE_1.C,
TABLE_1.B,
TABLE_1.E,
TABLE_1.F
FROM
TABLE_1
WHERE
( TABLE_1.F IN ( 'STATE1', 'STATE2' ) ) AND
( TABLE_1.B= 'SOMETEXT' ) AND
( TABLE_1.C=1 ) AND
( TABLE_1.E= 'IN' ) AND
( TABLE_1.D IS NULL )
ORDER BY
TABLE_1.ID
)
WHERE
( ROWNUM <= 150 )
) TOPNWRAPPER
WHERE
( TOPNWRAPPER.ID = TABLE_2.T1_ID_FK )
ORDER BY
TOPNWRAPPER.ID ASC
Stats:
|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem ||
|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|| 0 ||SELECT STATEMENT | | 1 | | 150 |00:00:00.01 | 1076 | | | ||
|| 1 |||NESTED LOOPS | | 1 | | 150 |00:00:00.01 | 1076 | | | ||
|| 2 ||||NESTED LOOPS | | 1 | 1 | 150 |00:00:00.01 | 926 | | | ||
|| 3 |||||VIEW | | 1 | 1 | 150 |00:00:00.01 | 176 | | | ||
||* 4 ||||||COUNT STOPKEY | | 1 | | 150 |00:00:00.01 | 176 | | | ||
|| 5 |||||||VIEW | | 1 | 1 | 150 |00:00:00.01 | 176 | | | ||
||* 6 ||||||||SORT ORDER BY STOPKEY | | 1 | 1 | 150 |00:00:00.01 | 176 | 15360 | 15360 |14336 (0)||
|| 7 |||||||||INLIST ITERATOR | | 1 | | 165 |00:00:00.01 | 176 | | | ||
||* 8 ||||||||||TABLE ACCESS BY INDEX ROWID| TABLE_1 | 2 | 1 | 165 |00:00:00.01 | 176 | | | ||
||* 9 |||||||||||INDEX RANGE SCAN | TABLE_1_B_E_F_ID | 2 | 50 | 165 |00:00:00.01 | 11 | | | ||
||* 10 |||||INDEX RANGE SCAN | TABLE_2_T1_ID_FK | 150 | 1 | 150 |00:00:00.01 | 750 | | | ||
|| 11 ||||TABLE ACCESS BY INDEX ROWID | TABLE_2 | 150 | 1 | 150 |00:00:00.01 | 150 | | | ||
|-------------------------------------------------------------------------------------------------------------------------------------------------------|
| |
|Predicate Information (identified by operation id): |
|--------------------------------------------------- |
| |
| 4 - filter(ROWNUM<=150) |
| 6 - filter(ROWNUM<=150) |
| 8 - filter((“TABLE_1”.”C”=1 AND “TABLE_1”.”D” IS NULL)) |
| 9 - access(“TABLE_1”.”B”='SOMETEXT' AND |
| “TABLE_1”.”E”='IN' AND ((“TABLE_1”.”F”='STATE1') OR (“TABLE_1”.”F”='STATE2')) |
| 10 - access(“TOPNWRAPPER”.”ID”=“TABLE_2”.”T1_ID_FK”) |
| |
+-------------------------------------------------------------------------------------------------------------------------------------------------------+
I've been looking around for a few days now. One suggestion I tried was using the hint /*+ USE_CONCAT (OR_PREDICATES(1)) */ and that helps a bit by reducing the mem usage by half, but it did not completely eliminate the problem.
EDIT: Was looking around (http://use-the-index-luke.com/sql/sorting-grouping/indexed-order-by#tip-ixord-full) and think this might be due to the order by. If I change the order by statements to be: TABLE_1.F,TABLE_1.ID and TOPNWRAPPER.F,TOPNWRAPPER.ID ASC then the sort operation goes away, unfortunately I need the top n rows based on ID. Alternatively I tried making a new index on (ID F) to test with and it did also remove the sort operation, but ID is unique per row making the table access operations less effective.
EDIT 2:
OPERATION |OPTION |CPU COST
--------------------------------------------
SORT |ORDER BY STOPKEY |21042774
|NESTED LOOPS |OUTER |56052
||TABLE ACCESS |BY INDEX ROWID |38980
|||INDEX |RANGE SCAN |30086
TOPNWRAPPER.ID- Juan Carlos Oropeza