У меня довольно простой запрос
SELECT TOP 1 dc.DOCUMENT_ID,
dc.COPIES,
dc.REQUESTOR,
dc.D_ID,
cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER
Это дает мне ужасную производительность (как никогда не удосужился дождаться его окончания). План запроса выглядит следующим образом:
Однако, если я удаляю, TOP 1
я получаю план, который выглядит так, и он выполняется через 1-2 секунды:
Правильный PK и индексация ниже.
Тот факт, что TOP 1
измененный план запроса меня не удивляет, я просто немного удивлен, что он делает его намного хуже.
Примечание. Я прочитал результаты этого поста и понял, что такое Row Goal
и т. Д. Мне интересно узнать, как я могу изменить запрос, чтобы он использовал лучший план. В настоящее время я сбрасываю данные во временную таблицу, а затем извлекаю из нее первую строку. Мне интересно, есть ли лучший метод.
Редактировать Для людей, читающих это после факта, здесь есть несколько дополнительных частей информации.
- Document_Queue - PK / CI является D_ID и имеет ~ 5 тыс. Строк.
- Correspondence_Journal - PK / CI имеет значение FILE_NUMBER, CORRESPONDENCE_ID и имеет ~ 1,4 млн строк.
Когда я начинал, других индексов не было. Я получил один в Correspondence_Journal (Document_Id, File_Number)
DOCUMENT_ID
связь между двумя таблицами (или у каждой записиCORRESPONDENCE_JOURNAL
есть соответствующая записьDOCUMENT_QUEUE
)?Ответы:
Попробуйте форсировать хеш- соединение *
Оптимизатор, вероятно, думал, что цикл с топ-1 будет лучше, и это имеет смысл, но в действительности это не сработало. Здесь только предположение, но, возможно, предполагаемая стоимость этой катушки была отключена - она использует TEMPDB - у вас может быть плохо работающая TEMPDB.
* Будьте осторожны с присоединиться к намекам , потому что они заставляют порядок доступа к таблице плана в соответствии с письменными порядка таблиц в запросе (так же , как если
OPTION (FORCE ORDER)
бы были указано). Из ссылки на документацию:Это не может привести к нежелательным эффектам в примере, но в целом это вполне возможно.
FORCE ORDER
(подразумеваемый или явный) - очень мощный намек, выходящий за рамки обеспечения порядка; это предотвращает применение широкого спектра методов оптимизатора, включая частичное агрегирование и переупорядочение.OPTION (HASH JOIN)
Запроса намек может быть менее навязчивой в подходящих случаях, так как это не подразумеваетFORCE ORDER
. Однако он применяется ко всем соединениям в запросе. Другие решения доступны.источник
Поскольку вы получаете правильный план с
ORDER BY
, может быть, вы могли бы просто бросить свой собственныйTOP
оператор?На мой взгляд, план запроса для
ROW_NUMBER()
вышеупомянутого должен быть таким же, как если бы у вас былORDER BY
. План запроса теперь должен иметь сегмент, проект последовательности и, наконец, оператор фильтра, а остальные должны выглядеть так же, как ваш хороший план.источник
Редактировать: +1 работает в этой ситуации, потому что оказывается, что
FILE_NUMBER
это строковая версия целого числа с нулевым дополнением. Лучшим решением здесь для строк является добавление''
(пустая строка), так как добавление значения может повлиять на порядок, или для чисел добавление чего-то, что является константой, но содержит недетерминированную функцию, напримерsign(rand()+1)
. Идея «сломать сортировку» все еще актуальна, просто мой метод не был идеальным.+1
Нет, я не имею в виду, что я согласен ни с чем, я имею в виду это как решение. Если вы измените свой запрос на,
ORDER BY cj.FILE_NUMBER + 1
то онTOP 1
будет вести себя по-другому.Видите ли, с целью небольшого ряда для упорядоченного запроса, система будет пытаться использовать данные по порядку, чтобы избежать использования оператора сортировки. Это также позволит избежать построения хеш-таблицы, полагая, что, вероятно, не нужно делать слишком много работы, чтобы найти эту первую строку. В вашем случае это неверно - из-за толщины этих стрелок кажется, что для поиска одного совпадения приходится использовать много данных.
Толщина этих стрелок говорит о том, что ваша
DOCUMENT_QUEUE
таблица (DQ) намного меньше вашейCORRESPONDENCE_JOURNAL
таблицы (CJ). И что лучший план на самом деле будет проверять через строки DQ, пока не будет найдена строка CJ. В самом деле, именно это и сделал бы Query Optimizer (QO), если бы в нем не было этого противногоORDER BY
, что хорошо поддерживается индексом покрытия на CJ.Поэтому, если вы отбросите
ORDER BY
полностью, я ожидаю, что вы получите план, который включает в себя вложенный цикл, перебирая строки в DQ, пытаясь найти CJ, чтобы убедиться, что строка существует. И сTOP 1
этим это прекратилось бы после того, как был потянут один ряд.Но если вам действительно нужна первая строка по
FILE_NUMBER
порядку, то вы могли бы заставить систему игнорировать этот индекс, который (неправильно) кажется очень полезным, выполняяORDER BY CJ.FILE_NUMBER+1
- что, как мы знаем, будет поддерживать тот же порядок, что и раньше, но, что важно, QO не делает. QO будет сосредоточен на получении полного набора, так что оператор Top N Sort может быть удовлетворен. Этот метод должен создать план, который содержит оператор Compute Scalar для определения значения порядка и оператор Top N Sort для получения первой строки. Но справа от них вы должны увидеть хороший Nested Loop, выполняющий множество поисков на CJ. И лучшая производительность, чем пробежка по большой таблице строк, которые ничего не соответствуют в DQ.Hash Match не обязательно ужасен, но если набор строк, которые вы возвращаете из DQ, намного меньше, чем CJ (как я и ожидал), то Hash Match будет сканировать намного больше CJ чем это нужно.
Примечание: я использовал +1 вместо +0, потому что оптимизатор запросов может распознать, что +0 ничего не меняет. Конечно, то же самое может относиться к +1, если не сейчас, то в какой-то момент в будущем.
источник
Добавление
OPTION (QUERYTRACEON 4138)
отключает эффект целей строк только для этого запроса, не слишком излишне предписывая окончательный план, и, вероятно, будет самым простым / наиболее прямым способом.Если добавление этой подсказки дает вам ошибку разрешений (требуется для
DBCC TRACEON
), вы можете применить ее, используя руководство плана:Использование
QUERYTRACEON
в направляющих план по spaghettidba... или просто используйте хранимую процедуру:
Какие разрешения
QUERYTRACEON
нужны? по Кендра Литтлисточник
Более новые версии SQL Server предлагают различные (и, возможно, лучшие) варианты для работы с запросами, которые получают неоптимальную производительность, когда оптимизатор может применять оптимизацию цели строки. SQL Server 2016 с пакетом обновления 1 (SP1) представил тот
DISABLE_OPTIMIZER_ROWGOAL USE HINT
же эффект, что и флаг трассировки 4138. Если вы не используете эту версию, вы также можете рассмотреть возможность использованияOPTIMIZE FOR
подсказки запроса, чтобы получить план запроса, предназначенный для возврата всех строк вместо 1. Запрос ниже. выдаст те же результаты, что и в вопросе, но он не будет создан с целью получить только 1 строку.источник
Поскольку вы делаете
TOP(1)
, я рекомендую сделатьORDER BY
детерминированный для начала. По крайней мере, это обеспечит предсказуемость результатов (всегда полезно для регрессионного тестирования). Похоже, нужно добавитьDC.D_ID
иCJ.CORRESPONDENCE_ID
для этого.Просматривая планы запросов, я иногда нахожу поучительным упростить запрос: возможно, заранее выбрать все соответствующие строки постоянного тока во временную таблицу, чтобы избежать проблем с оценкой мощности на
QUEUE_DATE
иPRINT_LOCATION
. Это должно быть быстро, учитывая низкое количество строк. Затем вы можете добавить индексы к этой временной таблице, если это необходимо, без изменения постоянной таблицы.источник