Oracle - Условная проблема с предложением где - PullRequest
0 голосов
/ 30 января 2019

Рассматривая две таблицы, объединенные таким образом:

select *
from   table_1
left   join table_2
on     table_1.lnk_id = table_2.lnk_id
;

Существует индекс "lnk_id" для table_1 и table_2.Это возвращает 80 миллионов строк.

Я использую условное предложение where с переменными, установленными моим внешним интерфейсом (APEX):

: all: If = 1, должно возвращать все строки.

: требуемый_ид: объект, который я хочу вернуть.Может быть нулевым значением, и в этом случае я хочу вернуть только строки с нулевым значением.

Сначала я закодировал это:

select *
from   table_1
left   join table_2
on     table_1.some_id = table_2.some_id

where  (
         case when :all = 1
              then 1
              when :desired_id is null and table_2.desired_id is null
              then 1
              when :desired_id = table_2.desired_id
              then 1
              else 0
              end = 1
       )

Учитывая: все = 0 и: требуемый_ид = некоторые не-Нулевое значение для выбора строк, которые желает пользователь, я испытываю ужасную производительность.

Я узнал, что мне нужно избегать выражения "case" в "where", поэтому он адаптирован к:

where (
        :all = 1
        or (:desired_id is null and table_2.desired_id is null)
        or :desired_id = table_2.desired_id
      )

НетВероятно, это так же медленно, как решение "дела".

Я понял это:

where  (:desired_id = table_2.desired_id);

-> 0,047 с - Супер быстро

where  (:desired_id = table_2.desired_id or 0 = 1);

--> 0,062 с - Сверхбыстрый

where  (:desired_id = table_2.desired_id or :all = 1);

-> 235 с - Сверхскоростной

Так что я точно могу найти нужный объект в 80-рядных строкахвремя с конструкцией where (... = ... или ... = 1): оптимизатор должен принять неправильное решение, когда я использую: all.

Кто-нибудь сможет руководить?

Я бы предпочел избегать решения build-a-dynamic-query, если это возможно, так как оно делает его более сложным для реализации и управления, я считаю, и это действительно звучит как ...должен работать с простым SQL.

- Изменить, чтобы добавить планы -

Хороший план

select *
from   table_2
left   join table_1
on     table_2.lnk_id = table_1.lnk_id
where  (:desired_id = table_1.desired_id or 0 = 1);


Plan hash value: 1995399472

--------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name             | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                  |   129 | 42183 |    45   (0)| 00:00:01 |       |       |
|   1 |  NESTED LOOPS                         |                  |   129 | 42183 |    45   (0)| 00:00:01 |       |       |
|   2 |   NESTED LOOPS                        |                  |   138 | 42183 |    45   (0)| 00:00:01 |       |       |
|   3 |    TABLE ACCESS BY INDEX ROWID BATCHED| TABLE_1          |     3 |   435 |     7   (0)| 00:00:01 |       |       |
|*  4 |     INDEX RANGE SCAN                  | TABLE_1_I1       |     3 |       |     3   (0)| 00:00:01 |       |       |
|*  5 |    INDEX RANGE SCAN                   | TABLE_2_I2       |    46 |       |     3   (0)| 00:00:01 |       |       |
|   6 |   TABLE ACCESS BY GLOBAL INDEX ROWID  | TABLE_2          |    40 |  7280 |    21   (0)| 00:00:01 | ROWID | ROWID |
--------------------------------------------------------------------------------------------------------------------------



Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("TABLE_1"."DESIRED_ID"=:DESIRED_ID)
   5 - access("TABLE_2"."LNK_ID"="TABLE_1"."LNK_ID")

Неправильный план

explain plan for
select *
from   table_2
left   join table_1
on     table_2.lnk_id = table_1.lnk_id
where    (:desired_id = table_1.desired_id or :p3070100_all = 1);

Plan hash value: 94704160

----------------------------------------------------------------------------------------------------------------
| Id  | Operation              | Name          | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |               |    79M|    24G|       |  1441K  (1)| 00:00:57 |       |       |
|*  1 |  FILTER                |               |       |       |       |            |          |       |       |
|*  2 |   HASH JOIN RIGHT OUTER|               |    79M|    24G|   484M|  1441K  (1)| 00:00:57 |       |       |
|   3 |    TABLE ACCESS FULL   | TABLE_1       |  3238K|   447M|       | 19152   (1)| 00:00:01 |       |       |
|   4 |    PARTITION RANGE ALL |               |    79M|    13G|       |   668K  (1)| 00:00:27 |     1 |1048575|
|   5 |     TABLE ACCESS FULL  | TABLE_2       |    79M|    13G|       |   668K  (1)| 00:00:27 |     1 |1048575|
----------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("TABLE_1"."DESIRED_ID"=:DESIRED_ID OR TO_NUMBER(:P3070100_ALL)=1)
   2 - access("TABLE_2"."LNK_ID"="TABLE_1"."LNK_ID"(+))   

Спасиботы, Дэвид

Ответы [ 3 ]

0 голосов
/ 31 января 2019

Взгляд на неправильный план объясняет причину проблемы, которая находится здесь:

1 - filter("TABLE_1"."DESIRED_ID"=:DESIRED_ID OR TO_NUMBER(:P3070100_ALL)=1) 

Оптимизатор не может использовать индекс TABLE_1_I1 из-за этого условия ИЛИ.


Не используйте эту переменную связывания TO_NUMBER(:P3070100_ALL)=1 в запросе вообще, используйте вместо этого динамический SQL и две версии запроса в зависимости от значения :P3070100.

Если:P3070100 <> 1 используйте этот запрос, который будет использовать индекс и будет быстрым:

select *
from   table_2
left   join table_1
on     table_2.lnk_id = table_1.lnk_id
where    :desired_id = table_1.desired_id ;

и когда :P3070100 = 1 будет использовать этот запрос, который будет медленным (поскольку он объединяет все строки из обеих таблиц):

select *
from   table_2
left   join table_1
on     table_2.lnk_id = table_1.lnk_id
0 голосов
/ 01 февраля 2019

Как писал Робертус, вам следует избегать использования «ИЛИ» в запросе, который работает с индексированными столбцами. Я предлагаю заменить следующий запрос на «ИЛИ»

select *
from   table_2
left   join table_1
on     table_2.lnk_id = table_1.lnk_id
where  (:desired_id = table_2.desired_id or :all = 1);

гораздо более мощным решением вусловия стоимости.

select *
from   table_2
left   join table_1
on     table_2.lnk_id = table_1.lnk_id
where  :desired_id = table_2.desired_id
  union
select *
from   table_2
left   join table_1
on     table_2.lnk_id = table_1.lnk_id
where  :desired_id <> table_2.desired_id
and    :all = 1
;
0 голосов
/ 31 января 2019

Когда вы добавляете or к вашему предложению where и частью этого условия является параметр или функция, база данных не может просто использовать индекс.Это потому, что есть «другая опция», а оптимизатор на основе затрат (CBO) часто выбирает «FULL TABLE SCAN».Вы можете сделать свой запрос более простым для CBO - например:

  1. на уровне приложения выберите создание предложения where на основе: all value,

  2. использовать некоторые приемы, чтобы условие выглядело просто - например, вместо использования: все параметры используют только: требуемый_идентификатор, а для получения всех результатов просто передайте в качестве значения «null», тогда вы можете сделать что-то вроде этого:1013 *

Если в table_2.desired_id есть индекс, CBO должен выбрать «сканирование диапазона» или «уникальное сканирование» (для уникального индекса).

Вы всегда должны генерировать план объяснения для ваших запросови ищите «полное сканирование», «вложенные циклы» и «декартовы объединения» с большими таблицами - это то, чего вам следует избегать.

Обновление (2019-02-01)

Есть3-й вариант, когда вы хотели бы иметь «все в одном запросе», поэтому без какой-либо дополнительной логики на уровне приложения (выбор между 2 запросами) или с использованием динамического SQL.Существует возможность сделать 2 запроса в одном с union all и записать их так, чтобы база данных всегда выполняла только одну часть.

Ниже приведена улучшенная версия запроса, предложенная JPG:

select *
from table_2
left join table_1 on table_2.lnk_id = table_1.lnk_id
where nvl(:all,2) != 1
  and :desired_id = table_2.desired_id
union all
select *
from table_2
left join table_1 on table_2.lnk_id = table_1.lnk_id
where :desired_id = table_2.desired_id
  and :all = 1
;

Примите во внимание, что объяснение может "показать" больше работы для этого, чем для других планов, но во время выполнения база данных должна разрешить одно из условий как "всегда ложное" и выполнить запрос только для одной части.

- Второе обновление -

Хорошо, я прочитал один из ваших комментариев, где вы написали:

«Все» возвращает каждыйстрока «None» возвращает все строки с нулевыми значениями для этого столбца, «конкретное значение» возвращает строки, соответствующие этому конкретному значению ...

Допустим, все могут принимать 3 значения:

  • 0 - что означает конкретную строку
  • 1 - что означает все строки
  • 2 - что означает все строки с NULL в table_2.desired_id

Тогдазапрос будет:

-- Specific row
select *
from   table_2
left   join table_1
on     table_2.lnk_id = table_1.lnk_id
where  :all = 0
  and  :desired_id = table_2.desired_id
union all
-- All rows with null
select *
from   table_2
left   join table_1
on     table_2.lnk_id = table_1.lnk_id
where  :all = 2
  and  table_2.desired_id is NULL
union all
-- All rows
select *
from   table_2
left   join table_1
on     table_2.lnk_id = table_1.lnk_id
where  :desired_id = table_2.desired_id
  and    :all = 1
;

Но вы должны знать, что простой индex не работает с NULL.Поэтому, если у вас есть индекс для table_2.desired_id:

create index idx_table_2_desired_id on table_2(desired_id);

Это не будет работать, но может сложным, например,

create index idx_table_2_desired_id on table_2(desired_id, 1);

позволит базе данных искать NULL втребуемый_ид в таком индексе.

...