Производительность запросов в SQL Server: ошибка #1
Использование OR внутри оператора JOIN или WHERE для нескольких столбцов
SQL Server может эффективно обрабатывать набор данных, если используется оператор WHERE или любая комбинация фильтров, разделенных AND. Будучи исключающими, эти операторы берут данные и нарезают их на все более мелкие части, пока не останется нужный набор данных.
Оператор OR – совсем другая история. SQL Server не может обработать его за одну операцию. Вместо этого каждый компонент OR рассмотривается отдельно. Когда каждая такая затратная операция завершена, результаты объединяются.
Сценарий, в котором OR работает хуже всего, – это когда задействовано несколько столбцов или таблиц. Даже если используется всего несколько таблиц или столбцов, производительность может стать крайне низкой.
Давайте рассмотрим простой пример использования OR, который приводит к плохой производительности:
SELECT DISTINCT PRODUCT.ProductID, PRODUCT.Name FROM Production.Product PRODUCT INNER JOIN Sales.SalesOrderDetail DETAIL ON PRODUCT.ProductID = DETAIL.ProductID OR PRODUCT.rowguid = DETAIL.rowguid;
Запрос достаточно прост: 2 таблицы и JOIN, который проверяет ProductID и rowguid. Даже если бы ни один из этих столбцов не был индексирован, обе таблицы Product и SalesOrderDetail были бы проверены. Вот итоговая производительность этого запроса:
Как мы видим, для обработки OR было использовано огромное количество ресурсов. Было выполнено 1,2 миллиона операций чтения! Учитывая, что Product содержит всего 504 строки, а SalesOrderDetail – 121317 строк, мы прочли гораздо больше данных, чем полное содержимое каждой из этих таблиц. Выполнение запроса заняло около 2 секунд на относительно быстром компьютере с SSD-диском.
Вывод из этого примера в том, что SQL Server не может легко обработать условие OR для нескольких столбцов. Лучший способ решить такую проблему c OR – это просто не использовать его (если возможно) или разбить запрос на части. Преобразование короткого и простого запроса на более длинные запросы может показаться не очень элегантным, но при решении проблем с OR это зачастую наилучшее решение:
SELECT PRODUCT.ProductID, PRODUCT.Name FROM Production.Product PRODUCT INNER JOIN Sales.SalesOrderDetail DETAIL ON PRODUCT.ProductID = DETAIL.ProductID UNION SELECT PRODUCT.ProductID, PRODUCT.Name FROM Production.Product PRODUCT INNER JOIN Sales.SalesOrderDetail DETAIL ON PRODUCT.rowguid = DETAIL.rowguid;
В этом запросе мы взяли каждый компонент OR и превратили его в собственный оператор SELECT. UNION объединяет набор результатов и удаляет дубликаты. Вот результирующая производительность:
План выполнения стал значительно сложнее, поскольку теперь мы запрашиваем каждую таблицу дважды, а не один раз. Однако количество чтений сократилось с 1,2 миллиона до 750, а запрос выполняется за секунду, а не за 2 секунды.
Обратите внимание, что в плане выполнения по-прежнему много сканирований индексов, но, несмотря на необходимость сканировать таблицы четыре раза, производительность намного выше, чем раньше.
Будьте осторожны при написании запросов с оператором OR. Протестируйте и убедитесь, что производительность адекватна и что вы случайно не создали проблему, подобную той, что мы наблюдали выше. Если вы анализируете плохо работающий запрос и сталкиваетесь с OR в разных столбцах или таблицах, проверьте такую возможную причину. Это легко обнаруживаемый тип запроса, часто приводящий к низкой производительности.
Оригинал статьи: https://www.sqlshack.com/query-optimization-techniques-in-sql-server-tips-and-tricks/