Задача примитивная, но не пойму, как решить. База Oracle 9i.
Есть две таблицы, один-ко-многим. Для примера customer -< order (у одного клиента много заказов). В обоих таблицах относительно много строк (миллионы в первой, десятки миллионов во второй). В таблице order есть высокоселективное поле (к примеру некий код товара), не уникальное, но поиск по нему выдаёт обычно несколько десятков строк.
create table customer (
id numeric primary key,
name varchar2(100) not null
);
create table order (
id numeric primary key,
customer_id numeric not null references customer(id),
product_number varchar2(20) not null
);
create index on order (customer_id);
create index on order (product_number);
Если я делаю select customer_id from order where product_number = :number, запрос использует индекс и отрабатывает моментально. Если я делаю select name from customer where id = :id, запрос использует индекс и тоже отрабатывает моментально. Мне интересен запрос вида
select customer.name
from customer
join order on customer.id = order.customer_id
where order.product_number = :number
То бишь логично использовать nested loops: сначала выбрать записи в order, пройти по ним и выбрать соответствующие записи в customer, из которых и вытащить name.
По факту база делает hash join. С одной стороны делает выборку по индексу в order, с другой стороны делает тупо полную выборку в customer. В итоге оно дико тормозит.
Пробовал менять порядок в запросе, делать разные варианты — не помогает. Пробовал использовать хинт /*+ use_nl */ и /*+ use_nl_with_index */, тоже не помогает.
Во-первых, если база на столь простом запросе строит явно неверный план — это значит либо то, что в ней категорически неадекватна статистика (и надо её пересобрать) либо то, что Вы чего-то не договариваете (например, высокая селективность оказывается только на некоторых особенных значениях поля).
Во-вторых, если база отказывается подчиняться хинту, это значит опять же одно из двух: либо он написан с ошибкой, либо запрос по каким-то причинам не может быть выполнен так, как его пытается заставить программист. В Вашем случае, я так думаю, первый вариант. А самый простой способ подтолкнуть базу к выбору nested loop — хинт first_rows (но самый правильный, по-прежнему — собрать таки статистику). Для сведения:
SQL> explain plan for select * from top_dev_style ds, top_dev_style_color dsc where ds.code = dsc.dev_style_code;
Explained
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------Plan hash value: 2471906211
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSp
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 336K| 55M|
|* 1 | HASH JOIN | | 336K| 55M| 22
| 2 | TABLE ACCESS STORAGE FULL| TOP_DEV_STYLE | 198K| 20M|
| 3 | TABLE ACCESS STORAGE FULL| TOP_DEV_STYLE_COLOR | 336K| 20M|
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("DS"."CODE"="DSC"."DEV_STYLE_CODE")
15 rows selected
SQL> explain plan for select/* use_nl(ds, dsc) */ * from top_dev_style ds, top_dev_style_color dsc where ds.code = dsc.dev_style_code;
Explained
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------Plan hash value: 2471906211
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSp
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 336K| 55M|
|* 1 | HASH JOIN | | 336K| 55M| 22
| 2 | TABLE ACCESS STORAGE FULL| TOP_DEV_STYLE | 198K| 20M|
| 3 | TABLE ACCESS STORAGE FULL| TOP_DEV_STYLE_COLOR | 336K| 20M|
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("DS"."CODE"="DSC"."DEV_STYLE_CODE")
15 rows selected
SQL> explain plan for select/*+ use_nl(ds, dsc) */ * from top_dev_style ds, top_dev_style_color dsc where ds.code = dsc.dev_style_code;
Explained
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------Plan hash value: 1924271327
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cos
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 336K| 55M| 3
| 1 | NESTED LOOPS | | 336K| 55M| 3
| 2 | TABLE ACCESS STORAGE FULL | TOP_DEV_STYLE_COLOR | 336K| 20M| 22
| 3 | TABLE ACCESS BY INDEX ROWID| TOP_DEV_STYLE | 1 | 108 |
|* 4 | INDEX UNIQUE SCAN | TOP_DEV_STYLE_PK | 1 | |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("DS"."CODE"="DSC"."DEV_STYLE_CODE")
16 rows selected
SQL> explain plan for select/*+ first_rows */ * from top_dev_style ds, top_dev_style_color dsc where ds.code = dsc.dev_style_code;
Explained
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------Plan hash value: 1924271327
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | By--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 336K|
| 1 | NESTED LOOPS | | 336K|
| 2 | TABLE ACCESS STORAGE FULL FIRST ROWS| TOP_DEV_STYLE_COLOR | 336K|
| 3 | TABLE ACCESS BY INDEX ROWID | TOP_DEV_STYLE | 1 |
|* 4 | INDEX UNIQUE SCAN | TOP_DEV_STYLE_PK | 1 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("DS"."CODE"="DSC"."DEV_STYLE_CODE")
16 rows selected
Re[2]: Как уговорить базу использовать nested loops?
Единственное, что я тут не договариваю, это, что таблица customer партиционирована (на самом деле там не customer, а логи, а order это привязанные к записи лога детали; партиционирование идёт по дате и я, честно говоря, сам не понимаю, зачем оно нужно, но так сделали деды). Но вроде это не должно никак влиять... За запросы спасибо, поэкспериментирую. Я писал /*+ use_nl */ (без дополнительных скобок).
select o.*, (select c.Name from customer c where c.id=o.customer_id)
from "ORDER" o
where o.product_number = :number;
Можно еще попробовать заставить в добровольно-принудительном порядке сервер представление делать(Без Хинта через аналит. фкнкцию)
Порой тоже помогает
select c."NAME", q.nn
from
(
select o.customer_id, row_number() over (order by o.customer_id) as NN
from "ORDER" o
where o.product_number = :number
) q
Join customer c on (c.id=q.customer_id)
use_nl(table1, table2) в итоге сработало, спасибо большое. Почему оно не использует этот вариант по умолчанию, не понятно, ну да ладно. Вариант IZM тоже работает, но там нужно много полей на самом деле, поэтому решил так не делать.
Re[2]: Как уговорить базу использовать nested loops?
Здравствуйте, vsb, Вы писали:
vsb>use_nl(table1, table2) в итоге сработало, спасибо большое. Почему оно не использует этот вариант по умолчанию, не понятно, ну да ладно. Вариант IZM тоже работает, но там нужно много полей на самом деле, поэтому решил так не делать.
То есть самый правильный совет (иметь актуальную статистику) ты решил проигнорировать?
Re[3]: Как уговорить базу использовать nested loops?
Здравствуйте, vsb, Вы писали:
vsb>Единственное, что я тут не договариваю, это, что таблица customer партиционирована (на самом деле там не customer, а логи, а order это привязанные к записи лога детали; партиционирование идёт по дате и я, честно говоря, сам не понимаю, зачем оно нужно, но так сделали деды). Но вроде это не должно никак влиять...
Еще как должно и еще как влияет. Главный вопрос: твой "высокоселективный" индекс так же партиционирован, как и таблица?
Re[3]: Как уговорить базу использовать nested loops?
Здравствуйте, wildwind, Вы писали:
vsb>>use_nl(table1, table2) в итоге сработало, спасибо большое. Почему оно не использует этот вариант по умолчанию, не понятно, ну да ладно. Вариант IZM тоже работает, но там нужно много полей на самом деле, поэтому решил так не делать.
W>То есть самый правильный совет (иметь актуальную статистику) ты решил проигнорировать?
Статистика собрана и пересобрана.
Re[4]: Как уговорить базу использовать nested loops?
Здравствуйте, wildwind, Вы писали:
vsb>>Единственное, что я тут не договариваю, это, что таблица customer партиционирована (на самом деле там не customer, а логи, а order это привязанные к записи лога детали; партиционирование идёт по дате и я, честно говоря, сам не понимаю, зачем оно нужно, но так сделали деды). Но вроде это не должно никак влиять...
W>Еще как должно и еще как влияет. Главный вопрос: твой "высокоселективный" индекс так же партиционирован, как и таблица?
В таблице customer первичный ключ по id. Индекс, насколько я понимаю, глобальный, не партиционирован.
Re[5]: Как уговорить базу использовать nested loops?