Короче говоря, мы обновляем маленькие таблицы людей со значениями из очень большой таблицы людей. В недавнем тесте это обновление заняло около 5 минут.
Мы наткнулись на то, что кажется самой глупой из возможных оптимизаций, которая, похоже, работает отлично! Тот же запрос теперь выполняется менее чем за 2 минуты и дает те же результаты, совершенно.
Вот запрос. Последняя строка добавляется как «оптимизация». Почему интенсивное сокращение времени запроса? Мы что-то упустили? Может ли это привести к проблемам в будущем?
UPDATE smallTbl
SET smallTbl.importantValue = largeTbl.importantValue
FROM smallTableOfPeople smallTbl
JOIN largeTableOfPeople largeTbl
ON largeTbl.birth_date = smallTbl.birthDate
AND DIFFERENCE(TRIM(smallTbl.last_name),TRIM(largeTbl.last_name)) = 4
AND DIFFERENCE(TRIM(smallTbl.first_name),TRIM(largeTbl.first_name)) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
AND LEFT(TRIM(largeTbl.last_name), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å')
Технические примечания. Нам известно, что для проверки списка букв может потребоваться еще несколько букв. Мы также знаем об очевидном поле для ошибки при использовании «РАЗНИЦА».
План запроса (обычный): https://www.brentozar.com/pastetheplan/?id=rypV84y7V
План запроса (с «оптимизацией»): https://www.brentozar.com/pastetheplan/?id=r1aC2my7E
AND LEFT(TRIM(largeTbl.last_name), 1) BETWEEN 'a' AND 'z' COLLATE LATIN1_GENERAL_CI_AI
делать то, что вы хотите, не требуя перечисления всех символов и иметь код, который трудно прочитатьWHERE
ложно? В частности, обратите внимание, что сравнение может быть чувствительным к регистру.Latin1_General_100_CI_AI
. А для SQL Server 2012 и новее (по крайней мере, через SQL Server 2019) лучше использовать сопоставления с поддержкой дополнительных символов в самой высокой версии для используемой локали. Так было быLatin1_General_100_CI_AI_SC
в этом случае. Версии> 100 (пока только японские) не имеют (или не нужны)_SC
(напримерJapanese_XJIS_140_CI_AI
).Ответы:
Это зависит от данных в ваших таблицах, ваших индексов ... Трудно сказать, не имея возможности сравнить планы выполнения / статистику времени io +.
Разница, которую я ожидаю, заключается в дополнительной фильтрации, происходящей перед соединением между двумя таблицами. В моем примере я изменил обновления для выбора для повторного использования моих таблиц.
План выполнения с «оптимизацией»
План выполнения
Вы четко видите, что происходит операция фильтрации, в моих тестовых данных нет записей, где отфильтровываются, и, как следствие, нет улучшений, где сделаны
План выполнения, без «оптимизации»
План выполнения
Фильтр пропал, а это значит, что нам придется полагаться на объединение, чтобы отфильтровать ненужные записи.
Другая (-ые) причина (-ы) Другая причина / следствие изменения запроса может заключаться в том, что при изменении запроса был создан новый план выполнения, который оказывается быстрее. Примером этого является движок, выбирающий другой оператор Join, но на данный момент это только предположение.
РЕДАКТИРОВАТЬ:
Уточнение после получения двух планов запроса:
Запрос читает 550M строк из большой таблицы и отфильтровывает их.
Это означает, что именно предикат выполняет большую часть фильтрации, а не предикат поиска. В результате данные читаются, но возвращаются меньше.
Заставить SQL Server использовать другой индекс (план запроса) / добавление индекса может решить эту проблему.
Так почему же запрос на оптимизацию не имеет такой же проблемы?
Потому что используется другой план запроса, с поиском вместо поиска.
Без каких-либо поисков, но только возвращая 4M строк для работы.
Следующая разница
Не обращая внимания на разницу в обновлении (в оптимизированном запросе ничего не обновляется), в оптимизированном запросе используется совпадение хеша:
Вместо неоптимизированного соединения с вложенным циклом:
Вложенный цикл лучше всего подходит, когда одна таблица маленькая, а другая большая. Поскольку они оба близки по размеру, я бы сказал, что в этом случае лучшим выбором будет совпадение по хешу.
обзор
Оптимизированный запрос
План оптимизированного запроса имеет параллелизм, использует соединение с хэш-соответствием и требует меньше остаточной фильтрации ввода-вывода. Он также использует растровое изображение для исключения значений ключей, которые не могут создавать строки соединения. (Тоже ничего не обновляется)
Неоптимизированный запрос План неоптимизированного запроса не имеет параллелизма, использует соединение с вложенным циклом и должен выполнять остаточную фильтрацию ввода-вывода для записей 550M. (Также происходит обновление)
Что вы можете сделать, чтобы улучшить неоптимизированный запрос?
Изменение индекса, чтобы в списке ключевых столбцов были указаны имя и фамилия:
CREATE INDEX IX_largeTableOfPeople_birth_date_first_name_last_name для dbo.largeTableOfPeople (дата рождения, имя_первого, фамилия) включают (id)
Но из-за использования функций и большой таблицы это может быть не оптимальным решением.
(HASH JOIN, MERGE JOIN)
к запросуТестовые данные + использованные запросы
источник
Не ясно, что второй запрос на самом деле является улучшением.
Планы выполнения содержат QueryTimeStats, которые показывают гораздо меньшую разницу, чем указано в вопросе.
У медленного плана прошло время
257,556 ms
(4 минуты 17 секунд). У быстрого плана прошло время190,992 ms
(3 минуты 11 секунд), несмотря на то, что он выполнялся со степенью параллелизма 3.Более того, второй план выполнялся в базе данных, где не было никакой работы после объединения.
Первый План
Второй План
Так что дополнительное время вполне может быть объяснено работой, необходимой для обновления 3,5 миллионов строк (работа, требуемая в операторе обновления, чтобы найти эти строки, заблокировать страницу, записать обновление на страницу и журнал транзакций, не является незначительной)
Если это на самом деле воспроизводимо при сравнении как с подобным, то объяснение состоит в том, что вам просто повезло в этом случае.
Фильтр с 37
IN
условиями исключил только 51 строку из 4008,334 в таблице, но оптимизатор посчитал, что это устранит гораздо большеТакие неправильные оценки мощности обычно плохие вещи. В этом случае он создал план (и параллельный) другой формы, который, по-видимому, (?) Работал лучше для вас, несмотря на разлив хеша, вызванный массивной недооценкой.
Без
TRIM
SQL Server можно преобразовать это в интервал диапазона в гистограмме базового столбца и дать гораздо более точные оценки, но при этомTRIM
он просто прибегает к догадкам.Природа предположения может варьироваться, но оценка для одного предиката
LEFT(TRIM(largeTbl.last_name), 1)
в некоторых случаях * просто оценивается какtable_cardinality/estimated_number_of_distinct_column_values
.Я не уверен точно, какие обстоятельства - размер данных, кажется, играет роль. Я смог воспроизвести это с широкими типами данных фиксированной длины, как здесь, но получил другое, более высокое, предположение с
varchar
(которое только использовало 10% -ое предположение и приблизительно 100 000 строк). @Solomon Rutzky указывает на то, что, если они дополнены концевымиvarchar(100)
пробелами, как это происходит дляchar
нижней оценки, используетсяIN
Список расширен, чтобыOR
и SQL Server использует экспоненциальную отсрочку максимум 4 предикатов рассмотренных. Таким образом,219.707
оценка получается следующим образом.источник