Странные изменения скорости с SQL-запросом - PullRequest
1 голос
/ 06 августа 2010

У меня проблемы с получением правильного запроса (оракула) для того, что я ищу.По сути, я хочу получить следующее:

SELECT  count(ck.id)
FROM    claim_key ck
WHERE   (ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY))
     AND ck.clm_type = 5
     AND ck.prgrm_id = 1

Объяснение:

| Id  | Operation                    |  Name            | Rows  | Bytes | Cost |
|   0 | SELECT STATEMENT             |                  |     1 |    14 |  3080|
|   1 |  SORT AGGREGATE              |                  |     1 |    14 |      |
|   2 |   TABLE ACCESS BY INDEX ROWID| CLAIM_KEY        |  6531 | 91434 |  3080|
|   3 |    INDEX SKIP SCAN           | I_CLAIM_KEY_001  |  1306K|       |  2813|

Этот запрос дает мне то, что я хочу (в среднем результат равен 20), но для его выполнения требуется 10 минут.

Следующий запрос не совсем завершен, но выполняется намного быстрее:

SELECT  count(ck.id)
FROM    claim_key ck
WHERE   (ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY))
     AND ck.clm_type = 5

Объясните:

| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
|   0 | SELECT STATEMENT     |             |     1 |    11 |  9195 |
|   1 |  SORT AGGREGATE      |             |     1 |    11 |       |
|   2 |   TABLE ACCESS FULL  | CLAIM_KEY   | 19592 |   210K|  9195 |

Это также возвращает около 20, хотя это простоя не могу положиться на него, мне нужно включить prgrm_id.Дело в том, что это займет всего 20 секунд.

Следующий запрос не то, что я ищу, но дает представление о производительности:

SELECT  count(ck.id)
FROM    claim_key ck
WHERE   (ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY))
| Id  | Operation             |  Name            | Rows  | Bytes | Cost  |
|   0 | SELECT STATEMENT      |                  |     1 |     8 |     4 |
|   1 |  SORT AGGREGATE       |                  |     1 |     8 |       |
|   2 |   INDEX FAST FULL SCAN| I_CLAIM_KEY_002  |   195K|  1530K|     4 |

Это также занимает20 секунд, но в среднем возвращается 700 записей.Таблица request_key содержит около 25 миллионов строк.

В этой таблице несколько индексов.Вот они:

IX_CLAIM_KEY_CREATED: CREATED_ON
I_CLAIM_KEY_001: CLNC_STE_ID, PRVDR_ID, PRGRM_ID, UPDATED_ON
I_CLAIM_KEY_002: SRCE_ID, PRVDR_ID, CLNC_ID, DTE_OF_SRVCE, PRGRM_ID
I_CLAIM_KEY_003: CLNT_ID, DTE_OF_SRVCE
I_CLAIM_KEY_004: TRNSMSN_ID, CLM_STTS
I_CLAIM_KEY_005: UPDATED_ON
I_CLAIM_KEY_006: PRVDR_ID, CMN_SRCE_ID
PK_CLAIM_ID: ID

Я хочу знать, почему добавление prgrm_id так сильно его замедляет?Я ожидал бы, что это будет довольно быстро, так как он должен был бы искать только в 700 строках, указанных в (ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY)).Это неверное предположение?

Редактировать

Используя подсказку /*+ FULL(ck) */ в первом запросе, его время выполнения уменьшается и генерируетсяследующий план.

| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
|   0 | SELECT STATEMENT     |             |     1 |    14 |  9195 |
|   1 |  SORT AGGREGATE      |             |     1 |    14 |       |
|   2 |   TABLE ACCESS FULL  | CLAIM_KEY   |  6531 | 91434 |  9195 |

Ответы [ 5 ]

4 голосов
/ 06 августа 2010

Чтобы лучше понять, что происходит, попробуйте это:

explain plan set statement_id = 'query1' for
SELECT  count(ck.id)
FROM    claim_key ck
WHERE   (ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY))
     AND ck.clm_type = 5
     AND ck.prgrm_id = 1;

и затем:

select *
from table(dbms_xplan.display(statement_id=>'query1'));

Полагаю, вы увидите строку, указывающую, что TABLE ACCESS FULL заполнен на Claim_key.

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

explain plan set statement_id = 'query2' for
SELECT  count(ck.id)
FROM    claim_key ck
WHERE   (ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY))
     AND ck.clm_type = 5;

select *
from table(dbms_xplan.display(statement_id=>'query2'));

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


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

В INDEX SKIP SCAN база данных может попытаться использовать индекс, даже если ведущий столбец индекса даже не используется. В основном, если ваш индекс выглядел так (слишком упрощенно):

COL1   COL2   ROWID
A      X      1        <--
A      Y      2
A      Z      3
B      X      4        <--
B      Y      5
B      Z      6 

и ваше состояние было ГДЕ col2 = 'X', сканирование с пропуском индекса говорит, что просматривайте каждую комбинацию в COL1, где col2 = 'X'. Он «пропускает» значения в столбце col1, как только найдет совпадение (например, col1 = A, col2 = X), до тех пор, пока значение не меняется (col1 = B, затем col1 = C и т. Д.), И ищет дополнительные совпадения.

Загвоздка в том, что индексы (как правило!) Работают так: 1) найти следующий идентификатор строки в индексе, где было найдено значение 2) перейти к блоку таблицы с этим rowid (TABLE ACCESS BY INDEX ROWID) 3) повторять до тех пор, пока больше не будет найдено совпадений.

(Для сканирования с пропуском это также повлечет за собой затраты на выяснение, где находится следующее изменение значения для ведущих столбцов.)

Это все хорошо для небольшого числа рядов, но страдает от закона убывающей отдачи; это не так здорово, когда у вас есть большое количество строк. Это потому, что он должен прочитать блок индекса, затем блок таблицы, затем блок индекса, блок таблицы (даже если блок таблицы был прочитан ранее).

Полное сканирование таблицы просто «пропускает» данные, отчасти благодаря ... многоблочному считыванию. База данных может читать много блоков с диска за одно чтение и не читает один и тот же блок более одного раза.

INDEX FAST FULL SCAN в основном рассматривает I_CLAIM_KEY_002 как таблицу. На все то, что вам нужно в запросе, можно ответить только по индексу; нет доступа к таблице не требуется. (Я предполагаю, что I_CLAIM_KEY_002 определен как clnt_id, dte_of_srvce, и либо clnt_id, либо dte_of_srvce не могут иметь значение null. Так как ck.id должен быть ненулевым атрибутом, число в ck.id равно количеству в ck.clnt_id.)

Что касается исходного запроса, если вы не хотите перенастроить свои индексы, попробуйте следующее:

SELECT  /*+ FULL(ck) */ count(ck.id)
FROM    claim_key ck
WHERE   (ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY))
     AND ck.clm_type = 5
     AND ck.prgrm_id = 1

, что приведет к полному сканированию таблицы для request_key (ck), и вы увидите производительность, аналогичную двум другим. (Убедитесь, что это так, сначала добавьте в запрос префикс «объяснение плана набора Statement_id = 'query_hint' for» и запустите запрос dbms_xplan перед его выполнением.)

(Теперь вы спросите: «Хочу ли я постоянно добавлять подобные подсказки?», Пожалуйста, не делайте этого. Это только для теста. Это просто для проверки, лучше ли FTS, чем INDEX SKIP SCAN. Если это так, то вам нужно выяснить, почему.:)

В любом случае ... Я надеюсь, что это чуток ... Я имею в виду.

1 голос
/ 06 августа 2010

Мне кажется, что ни один из индексов, показанных в вашем посте, не очень полезен.Я ожидаю, что он будет выполнять полное сканирование таблицы, несмотря ни на что.

Если вы можете, добавьте индекс в

DTE_OF_SRVCE, CLM_TYPE, PRGRM_ID

и посмотрите, поможет ли это.Если вы хотите попробовать поиск только по индексу, добавьте идентификатор в конец индекса (таким образом, это будет DTE_OF_SRVCE, CLM_TYPE, PRGRM_ID, ID).

Поделитесь и наслаждайтесь.

0 голосов
/ 06 августа 2010

Что произойдет, если вы попробуете что-то вроде ...

SELECT COUNT(*)
    FROM (SELECT ck.id,
                 ck.prgrm_id AS prgrm_id
              FROM claim_key ck
              WHERE (ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY)) AND
                    ck.clm_type = 5) AS sq
    WHERE sq.prgrm_id = 1;

Я не могу попробовать что-то подобное дома, поэтому это может быть бесполезно, но это может помочь.

0 голосов
/ 06 августа 2010

Возможно, здесь указано очевидное, но повторяются ли эти результаты, или вы пробовали это один раз в порядке, указанном в вашем вопросе?Если так, то блочное кэширование может объяснить различия.

0 голосов
/ 06 августа 2010

Возможно, это потому, что cInt_id присутствует только в вашем третьем индексе. И еще он будет использовать второй

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...