Прежде всего вы должны использовать немного более новую технику для проверки плана выполнения, например, DBMS_XPLAN.DISPLAY`
EXPLAIN PLAN SET STATEMENT_ID = 'sqlx' into plan_table FOR
select
a.id,b.id,(select c.id from tab_c c where c.id = a.id)
from
tab_a a join tab_b b on a.id = b.id;
SELECT * FROM table(DBMS_XPLAN.DISPLAY('plan_table', 'sqlx','ALL'));
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 2600 | 7 (15)| 00:00:01 |
|* 1 | TABLE ACCESS FULL | TAB_C | 1 | 13 | 3 (0)| 00:00:01 |
|* 2 | HASH JOIN | | 100 | 2600 | 7 (15)| 00:00:01 |
| 3 | TABLE ACCESS FULL| TAB_A | 100 | 1300 | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS FULL| TAB_B | 100 | 1300 | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("C"."ID"=:B1)
2 - access("A"."ID"="B"."ID")
Это дает вам информацию о том, как осуществляется доступ к TAB_C
. Вы видите в Информация о предикате для строки 1 filter("C"."ID"=:B1)
.
Другими словами, вы будете полностью сканировать таблицу для каждого ID
, объединенного между таблицами A и B. Что, конечно, нежелательно.
Если вы не доверяете этому простому, запустите запрос и соберите статистику плана
select /*+ gather_plan_statistics */
a.id,b.id,(select c.id from tab_c c where c.id = a.id)
from
tab_a a join tab_b b on a.id = b.id;
---
select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));
SQL_ID 4m4a1cp4gyjkv, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ a.id,b.id,(select c.id from
tab_c c where c.id = a.id) from tab_a a join tab_b b on a.id =
b.id
Plan hash value: 2606630813
-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 100 |00:00:00.01 | 15 | | | |
|* 1 | TABLE ACCESS FULL | TAB_C | 100 | 1 | 100 |00:00:00.01 | 700 | | | |
|* 2 | HASH JOIN | | 1 | 100 | 100 |00:00:00.01 | 15 | 1517K| 1517K| 1256K (0)|
| 3 | TABLE ACCESS FULL| TAB_A | 1 | 100 | 100 |00:00:00.01 | 7 | | | |
| 4 | TABLE ACCESS FULL| TAB_B | 1 | 100 | 100 |00:00:00.01 | 8 | | | |
-----------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("C"."ID"=:B1)
2 - access("A"."ID"="B"."ID")
В строке 1 вы видите начинается = 100, что означает 100 раз, когда FULL SCAN
был инициирован.
Внимание - план выполнения может меняться в зависимости от статистики таблицы, настроек оптимизатора или версии Oracle (например, Oracle может переписать подзапрос и использовать объединение).
Это только пример с фиктивными таблицами на 11.2.
Но у вас должно сложиться впечатление, как наблюдать за поведением Oracle и решить, нужен ли вам дополнительный индекс или нет.