#oracle #oracle12c
Вопрос:
У меня есть таблица аудита, как показано ниже.
create table "AUDIT_LOG" ( "AUDIT_ID" NVARCHAR2(70), "PAYMENT_IDENTIFICATION_ID" NVARCHAR2(70), "ACCOUNT_NUMBER" NVARCHAR2(100) PRIMARY KEY ("AUDIT_ID") );
У меня ниже индекс
- payment_idx вкл. («PAYMENT_IDENTIFICATION_ID»)
- payment_id_idx вкл. («PAYMENT_IDENTIFICATION_ID», «AUDIT_ID»)
- system_index для первичного ключа AUDIT_ID
Ниже приведены запросы, которые я использую
Query1 : Select * FROM AUDIT_LOG WHERE PAYMENT_IDENTIFICATION_ID = 'ID124' AND AUDIT_IDlt;gt;'ecfdc2c3-87eb-48c9-b53c'; Query2 : Select * FROM AUDIT_LOG WHERE PAYMENT_IDENTIFICATION_ID = 'ID124' AND AUDIT_ID='ecfdc2c3-87eb-48c9-b53c';
Первый план объяснения запроса показывает использование индекса payment_id_idx с возможностью ПАКЕТНОЙ обработки по ИНДЕКСУ ROWID.
Однако второй запрос объясняет план, показывающий использование system_index для идентификатора первичного ключа AUDIT_ID с параметром ПО ПАКЕТУ ИДЕНТИФИКАТОРОВ СТРОК ИНДЕКСА.
Я придерживался мнения, что в обоих запросах следует использовать индекс payment_id_idx. Есть идеи, почему второй запрос не использует составной индекс payment_id_idx. Мы очень ценим любую помощь.
Комментарии:
1. «Я придерживался мнения, что в обоих запросах следует использовать индекс payment_id_idx» — почему у вас такое мнение?
2. Второй запрос использует первичный ключ, поэтому лучше всего использовать индекс первичного ключа, чтобы получить одну строку.
3. @TonyAndrews уловил твою мысль
4. каково распределение значений в столбцах, которые не являются первичным ключом ? сколько у вас различных значений в этих полях ?
5. @Mat в соответствии с моими знаниями для использования составного индекса у нас должна быть аналогичная колонка, в которой условие с правильным порядком
Ответ №1:
Давайте попробуем смоделировать сценарий, аналогичный вашему.
SQLgt; alter session set current_schema=test ; Session altered. SQLgt; create table "AUDIT_LOG" ( "AUDIT_ID" NVARCHAR2(70), "PAYMENT_IDENTIFICATION_ID" NVARCHAR2(70), "ACCOUNT_NUMBER" NVARCHAR2(100) ); 2 3 4 5 6 Table created. SQLgt; alter table audit_log add primary key ( audit_id ) ; Table altered. SQLgt; create index payment_idx on audit_log ("PAYMENT_IDENTIFICATION_ID"); Index created. SQLgt; create index payment_id_idx on audit_log ("PAYMENT_IDENTIFICATION_ID", "AUDIT_ID"); Index created.
Теперь давайте вставим некоторые демонстрационные данные, но, следуя некоторым соображениям:
- Идентификатор AUDIT_ID уникален в виде
IDxxx
( гдеxxx
принимает значения от 1 до 1 м ) - PAYMENT_IDENTIFICATION_ID принимает 10 различных значений в виде
LPAD
и буквы. Идея здесь состоит в том, чтобы сгенерировать 10 различных значений - ACCOUNT_NUMBER-это случайная строка из одной буквы и одной буквы в lpad, заполняющая 70 символов.
Таким образом
declare begin for i in 1 .. 1000000 loop insert into audit_log values ( 'ID'||i||'' , case when i between 1 and 100000 then lpad('A',50,'A') when i between 100001 and 200000 then lpad('B',50,'B') when i between 200001 and 300000 then lpad('C',50,'C') when i between 300001 and 400000 then lpad('D',50,'D') when i between 400001 and 500000 then lpad('E',50,'E') when i between 500001 and 600000 then lpad('F',50,'F') when i between 600001 and 700000 then lpad('G',50,'G') when i between 700001 and 800000 then lpad('H',50,'H') when i between 800001 and 900000 then lpad('I',50,'I') when i between 900001 and 1000000 then lpad('J',50,'J') end , lpad(dbms_random.string('U',1),70,'B') ); end loop; commit; end; /
Первый запрос
SQLgt; set autotrace traceonly lines 220 pages 400 SQLgt; Select * FROM AUDIT_LOG WHERE PAYMENT_IDENTIFICATION_ID = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' AND AUDIT_ID lt;gt; 'ID123482'; 2 3 4 5 6 100000 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 272803615 --------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 100K| 20M| 3767 (1)| 00:00:01 | |* 1 | TABLE ACCESS BY INDEX ROWID BATCHED| AUDIT_LOG | 100K| 20M| 3767 (1)| 00:00:01 | |* 2 | INDEX RANGE SCAN | PAYMENT_IDX | 100K| | 1255 (1)| 00:00:01 | --------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("AUDIT_ID"lt;gt;U'ID123482') 2 - access("PAYMENT_IDENTIFICATION_ID"=U'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAA') Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 16982 consistent gets 2630 physical reads 134596 redo size 12971296 bytes sent via SQL*Net to client 73843 bytes received via SQL*Net from client 6668 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 100000 rows processed
Второй запрос
SQLgt; set autotrace traceonly lines 220 pages 400 SQLgt; Select * FROM AUDIT_LOG WHERE PAYMENT_IDENTIFICATION_ID = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' AND AUDIT_ID ='ID578520'; 2 3 4 5 6 Execution Plan ---------------------------------------------------------- Plan hash value: 303326437 -------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 219 | 3 (0)| 00:00:01 | |* 1 | TABLE ACCESS BY INDEX ROWID| AUDIT_LOG | 1 | 219 | 3 (0)| 00:00:01 | |* 2 | INDEX UNIQUE SCAN | SYS_C0076603 | 1 | | 2 (0)| 00:00:01 | -------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("PAYMENT_IDENTIFICATION_ID"=U'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF FFFFFFFFFF') 2 - access("AUDIT_ID"=U'ID578520') Statistics ---------------------------------------------------------- 9 recursive calls 6 db block gets 9 consistent gets 7 physical reads 1080 redo size 945 bytes sent via SQL*Net to client 515 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
Информация о предикате предоставляет вам много информации о путях доступа:
В первом запросе:
1 - filter("AUDIT_ID"lt;gt;U'ID123482') 2 - access("PAYMENT_IDENTIFICATION_ID"=U'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAA')
Доступ определяется оператором»=», и в этом случае сканирование диапазона индекса PAYMENT_IDX является наилучшим подходом. Фильтр применяется для всех строк, соответствующих условию доступа, фильтруйте те, которыеlt;gt;, из значения в AUDIT_ID.
Во втором запросе:
1 - filter("PAYMENT_IDENTIFICATION_ID"=U'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF FFFFFFFFFF') 2 - access("AUDIT_ID"=U'ID578520')
Доступ осуществляется по индексу первичного ключа, который вы используете =
в качестве оператора, поэтому нет лучшего способа найти строку, использующую индекс PK. Вот почему у вас есть INDEX_UNIQUE_SCAN. Фильтр исходит из доступа к таблице, так как Oracle уже определила строку по уникальному индексу первичного ключа. На самом деле, это условие не является необходимым, так как, если вы не ищете 1 или ни одной строки.
Как и в первом запросе, вы делаете a lt;gt;
из индекса первичного ключа, Oracle будет использовать другой индекс. предполагая ( как в примере), что у вас очень мало различных значений. Имейте в виду, что в случае, если бы он использовал индекс PK, он получил бы 999999 строк на первом шаге, а затем применил фильтр, что гораздо менее эффективно, чем использование второго индекса.
Если вы заставите CBO использовать индекс PK, вы сможете увидеть это
SQLgt; Select /* INDEX(a,SYS_C0076603) */ * FROM AUDIT_LOG a WHERE PAYMENT_IDENTIFICATION_ID = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' AND AUDIT_ID lt;gt; 'ID123482'; 2 3 4 5 6 100000 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 3265638686 ---------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 100K| 20M| 207K (1)| 00:00:17 | |* 1 | TABLE ACCESS BY INDEX ROWID BATCHED| AUDIT_LOG | 100K| 20M| 207K (1)| 00:00:17 | |* 2 | INDEX FULL SCAN | SYS_C0076603 | 999K| | 3212 (1)| 00:00:01 | ---------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("PAYMENT_IDENTIFICATION_ID"=U'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AA') 2 - filter("AUDIT_ID"lt;gt;U'ID123482') Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 218238 consistent gets 18520 physical reads 1215368 redo size 12964630 bytes sent via SQL*Net to client 73873 bytes received via SQL*Net from client 6668 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 100000 rows processed
Комментарии:
1. Большое спасибо за такой подробный ответ. У меня есть один запрос, каким будет поведение, если («PAYMENT_IDENTIFICATION_ID») имеет одно значение.distinct.
2. это было бы то же самое. Лучший способ поблагодарить вас за ЭТО-принять и/или озвучить ответ, конечно, если он вам помог.