Один и тот же запрос с одним и тем же планом выполнения выполняется по-разному в разных средах Oracle

У меня есть следующий запрос:

SELECT t.*
FROM 
    (SELECT t.id, t.transaction_date
     FROM transactions t 
     ORDER BY t.transaction_date DESC, t.id DESC
     FETCH NEXT 11 ROWS ONLY) transactions_table 
JOIN 
    transactions t ON transactions_table.id = t.id
ORDER BY 
    t.transaction_date DESC, t.id DESC;

Столбец ID — это PRIMARY KEY таблицы, у меня есть следующий оператор CREATE INDEX:

CREATE INDEX transaction_date_idx ON transactions (transaction_date DESC, id);

План выполнения следующий:

PLAN_TABLE_OUTPUT
Plan hash value: 3772986339
 
---------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name                              | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                                   |    11 |   167K|       | 35981   (2)| 00:00:02 |
|   1 |  SORT ORDER BY                |                                   |    11 |   167K|       | 35981   (2)| 00:00:02 |
|   2 |   NESTED LOOPS                |                                   |    11 |   167K|       | 35980   (2)| 00:00:02 |
|   3 |    NESTED LOOPS               |                                   |    11 |   167K|       | 35980   (2)| 00:00:02 |
|*  4 |     VIEW                      |                                   |    11 |   286 |       | 35958   (2)| 00:00:02 |
|*  5 |      WINDOW SORT PUSHED RANK  |                                   |  4345K|   107M|   150M| 35958   (2)| 00:00:02 |
|   6 |       INDEX FAST FULL SCAN    | TRANSACTIONS_TRANSACTION_DATE_IDX |  4345K|   107M|       |  3593   (2)| 00:00:01 |
|*  7 |     INDEX UNIQUE SCAN         | PK_TRANSACTIONS                   |     1 |       |       |     1   (0)| 00:00:01 |
|   8 |    TABLE ACCESS BY INDEX ROWID| TRANSACTIONS                      |     1 | 15582 |       |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
"   4 - filter(""from$_subquery$_003"".""rowlimit_$$_rownumber""<=11)"
"   5 - filter(ROW_NUMBER() OVER ( ORDER BY SYS_OP_DESCEND(""TRANSACTION_DATE""),INTERNAL_FUNCTION(""T"".""ID"") DESC "
              )<=11)
"   7 - access(""T2"".""ID"" = ""from$_subquery$_003"".""ID"")"
 
Note
-----
   - dynamic statistics used: dynamic sampling (level=2)

У меня есть две среды с одинаковыми данными (~ 4 миллиона строк в таблице transactions). Первая среда, в которой запрос выполняется как положено (~ 500 мс). Во второй среде это занимает около 30 секунд! План выполнения для обеих сред абсолютно одинаков, за исключением:

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)

не печатаются на быстровозвратной среде. Когда я сравниваю внутренние запросы:

SELECT t.id, t.transaction_date
FROM transactions t 
ORDER BY t.transaction_date DESC, t.id DESC
FETCH NEXT 11 ROWS ONLY

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

Различные объемы данных и/или разная мощность значений индекса обычно дают разные планы запросов.

Bohemian 19.07.2024 07:30

Изменится ли мощность, если данные будут одинаковыми построчно?

Hasan Can Saral 19.07.2024 09:50

Нет. Идентичные данные должны создавать идентичный план запроса. В обеих системах обновите статистику по задействованным таблицам в соответствии с ответами.

Bohemian 19.07.2024 20:23

@Bohemian, но нам сказали, что план один и тот же для обоих.

William Robertson 20.07.2024 11:30

Вы видите, что делает база данных в течение 30 секунд? Проверьте события ожидания, посмотрите подробности в v$sql и (если есть лицензия) v$active_session_history.

William Robertson 20.07.2024 11:34

@WilliamRobertson Я обновлю вопрос, как только получу фактическое исполнение.

Hasan Can Saral 22.07.2024 10:36
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
2
6
88
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Скорее всего, Oracle выполняет хэш-соединение в одной среде, а не вложенные циклы в другой. Очень часто запрос ведет себя по-разному при выполнении в другой среде. Существует множество факторов, которые оптимизатор учитывает при разработке своего плана, поэтому нельзя ожидать, что каждый раз в двух разных базах данных будет один и тот же план.

Вот пара вещей, которые стоит попробовать:

  1. Вы действительно хотите FETCH NEXT, а не FETCH FIRST?

  2. Вероятно, вам вообще не следует использовать этот внутренний блок запросов. Упрощать:

    SELECT t.*
    FROM transactions t
    ORDER BY t.transaction_date DESC, t.id DESC
    FETCH FIRST 11 ROWS ONLY
    
  3. Для максимальной скорости переопределите индекс как (transaction_date,id). Это означает удаление ключевого слова DESC из определения индекса. Тогда намекните:

    SELECT /*+ INDEX_DESC(t) */ t.*
    FROM transactions t
    ORDER BY t.transaction_date DESC, t.id DESC
    FETCH FIRST 11 ROWS ONLY
    

Проверьте свой план и убедитесь, что там указано INDEX FULL SCAN DESCENDING (не INDEX FAST FULL SCAN), а над ним вы видите операцию WINDOW NOSORT STOPKEY (не WINDOW SORT PUSHED RANK). Вы пытаетесь избежать подобного.

Если вам нужно сделать это во вложенном блоке запроса, поскольку вам требуется присоединение к другим таблицам в родительском блоке, попробуйте что-то вроде этого:

SELECT /*+ USE_NL(t t2 t3) */ t.*,t2.*,t3.*
  FROM (SELECT /*+ NO_MERGE INDEX_DESC(t) */ ROWID row_id, t.id, t.transaction_date
         FROM transaction t 
        ORDER BY t.transaction_date DESC, t.id DESC
        FETCH FIRST 11 ROWS ONLY) transactions_table
       JOIN transaction t ON transactions_table.row_id = t.ROWID -- if self-joining
       LEFT OUTER JOIN transaction t2 ON t2.id = transactions_table.id -- other table join
       LEFT OUTER JOIN transaction t3 ON t3.id = transactions_table.id -- other table join
 ORDER BY transactions_table.transaction_date DESC, transactions_table.id DESC    

Обратите внимание на подсказки USE_NL и NO_MERGE. NO_MERGE предотвращает слияние представлений Oracle, которое сделало бы недействительным ваш INDEX_DESC-подсказку, а USE_NL обещает Oracle, что действительно лучше использовать вложенные циклы, а не хеш-соединение для этих объединений с другими таблицами. Обратите внимание, что я также дополнительно использовал ROWID для самосоединения. Это позволяет избежать повторного обращения к индексу, хотя вполне вероятно, что индекс был тем же самым, который использовался во внутреннем запросе, поэтому конечные блоки кэшируются, и выгода от его пропуска на втором проходе может быть незаметной.

Самообъединение действительно не требуется, поскольку вы можете вернуть все нужные столбцы из внутреннего запроса. Единственная ситуация, в которой я считаю полезным такое самостоятельное присоединение, — это когда я могу дисквалифицировать строки на основе критериев из других таблиц и хочу избежать затрат на чтение блоков из сегмента таблицы, пока я этого не сделаю. Ограничение внутреннего запроса ссылкой только на столбцы, являющиеся частью индекса, — вот что позволит реализовать этот трюк с производительностью.

Я перешел с помощью FETCH FIRST, вы правы (1). У меня есть пара LEFT JOIN для внешнего выбора, который может нарушить нумерацию страниц, из-за чего я не могу избавиться от внутреннего/внешнего выбора (2). Боюсь, изменение определения индекса на transaction_date, id, использование FETCH FIRST и подсказок индекса не помогли.

Hasan Can Saral 18.07.2024 20:50

Кроме того, эти две среды абсолютно идентичны друг другу: не только ресурсы и установка Oracle, но и данные. Я не считаю, что проблема связана с производительностью индекса или, скорее, с тем, используется индекс или нет. Я думаю, что мой индекс используется, но соединение все портит.

Hasan Can Saral 18.07.2024 20:51

@HasanCanSaral, вы также должны убедиться, что ваш внутренний блок запроса не объединяется, иначе подсказку можно проигнорировать. Добавьте подсказку no_merge. Чтобы посмотреть, сработает ли это. Я добавлю пример этого в свой ответ.

Paul W 18.07.2024 22:53

Боюсь NO_MERGEподсказка тоже не помогла.

Hasan Can Saral 19.07.2024 12:35

@HasanCanSaral, извини, я сказал, что вчера добавлю пример, но отвлекся и так и не сделал этого. Пример добавлен сейчас.

Paul W 19.07.2024 15:40

Лучший способ изучить разницу — получить активный отчет SQL Monitor и посмотреть, на что тратится время.

BobC 21.07.2024 20:03
Ответ принят как подходящий

Примечание dynamic statistics used: dynamic sampling (level=2) подразумевает, что в таблице отсутствует статистика по одному из ваших сред. Соберите статистику с помощью такого блока PL/SQL:

begin
    dbms_stats.gather_table_stats(ownname => user, tabname => 'TRANSACTIONS');
end;
/

Если это решит проблему, вам следует выяснить, почему статистика не собиралась автоматически. По умолчанию база данных Oracle каждый день автоматически собирает статистику по таблице, в которой есть новые данные или которая изменилась более чем на 10%. К сожалению, многие организации предпочитают отключать автозадачу по умолчанию и внедрять собственное решение.

Если это не решит проблему или вам интересно, почему это решило проблему, сверьте план выполнения с фактическими цифрами, а не с использованием догадок плана объяснения. Соберите данные, используя select dbms_sqltune.report_sql_monitor(sql_id => 'your SQL_ID') from dual;, и добавьте результаты к своему вопросу. Отчет о мониторинге SQL сообщит вам, какая операция занимает больше всего времени и каковы ожидания.

Я предполагаю, что для этой проблемы ваша «Активность (%)» не составит в сумме 100%. Когда это происходит, это означает, что время выполнения было потрачено на выполнение рекурсивного запроса; запрос, предназначенный для предоставления полезной информации, редко может занять больше времени, чем сам запрос. В частности, в редких случаях запрос динамической выборки может выполняться медленно. Возможно, сбор статистики позволит избежать необходимости выполнять дорогостоящий запрос динамической выборки.

Кроме того, я согласен с идеей Пола В. о переписывании запроса. Замена самосоединения более сложным запросом часто позволяет сэкономить много времени.

Уровень 2 является настройкой по умолчанию для динамической выборки, поэтому, если полученный план будет таким же, я не уверен, что это примечание актуально.

William Robertson 20.07.2024 11:37

@WilliamRobertson Уровень 2 используется и отображается в примечании только тогда, когда статистика таблицы или индекса полностью отсутствует. Поскольку планы в остальном те же, я согласен, что статистика вряд ли будет иметь прямое значение. Но пока что недостающая статистика — единственное известное различие между средами, поэтому их исчезновение, вероятно, стоит расследовать.

Jon Heller 20.07.2024 20:49

Это решило проблему. Спасибо.

Hasan Can Saral 27.07.2024 13:25

Другие вопросы по теме