У меня есть следующий запрос:
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, но нам сказали, что план один и тот же для обоих.
Вы видите, что делает база данных в течение 30 секунд? Проверьте события ожидания, посмотрите подробности в v$sql и (если есть лицензия) v$active_session_history.
@WilliamRobertson Я обновлю вопрос, как только получу фактическое исполнение.


Скорее всего, Oracle выполняет хэш-соединение в одной среде, а не вложенные циклы в другой. Очень часто запрос ведет себя по-разному при выполнении в другой среде. Существует множество факторов, которые оптимизатор учитывает при разработке своего плана, поэтому нельзя ожидать, что каждый раз в двух разных базах данных будет один и тот же план.
Вот пара вещей, которые стоит попробовать:
Вы действительно хотите FETCH NEXT, а не FETCH FIRST?
Вероятно, вам вообще не следует использовать этот внутренний блок запросов. Упрощать:
SELECT t.*
FROM transactions t
ORDER BY t.transaction_date DESC, t.id DESC
FETCH FIRST 11 ROWS ONLY
Для максимальной скорости переопределите индекс как (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 и подсказок индекса не помогли.
Кроме того, эти две среды абсолютно идентичны друг другу: не только ресурсы и установка Oracle, но и данные. Я не считаю, что проблема связана с производительностью индекса или, скорее, с тем, используется индекс или нет. Я думаю, что мой индекс используется, но соединение все портит.
@HasanCanSaral, вы также должны убедиться, что ваш внутренний блок запроса не объединяется, иначе подсказку можно проигнорировать. Добавьте подсказку no_merge. Чтобы посмотреть, сработает ли это. Я добавлю пример этого в свой ответ.
Боюсь NO_MERGEподсказка тоже не помогла.
@HasanCanSaral, извини, я сказал, что вчера добавлю пример, но отвлекся и так и не сделал этого. Пример добавлен сейчас.
Лучший способ изучить разницу — получить активный отчет SQL Monitor и посмотреть, на что тратится время.
Примечание 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 является настройкой по умолчанию для динамической выборки, поэтому, если полученный план будет таким же, я не уверен, что это примечание актуально.
@WilliamRobertson Уровень 2 используется и отображается в примечании только тогда, когда статистика таблицы или индекса полностью отсутствует. Поскольку планы в остальном те же, я согласен, что статистика вряд ли будет иметь прямое значение. Но пока что недостающая статистика — единственное известное различие между средами, поэтому их исчезновение, вероятно, стоит расследовать.
Это решило проблему. Спасибо.
Различные объемы данных и/или разная мощность значений индекса обычно дают разные планы запросов.