Если вы можете внести изменения в запрос, то удалите UPPER
- Это можно легко удалить, если вы используете сортировку без учета регистра (безусловно, самый распространенный случай) - в противном случае вам нужно будет добавить лог c, чтобы обеспечить добавление значений в верхнем регистре перед добавлением в запрос. UPPER
не является сложенным константой и может давать худшие планы, чем простые строковые литералы, как показано в различных примерах ниже.
Пример данных
CREATE TABLE [Table]
(
[COL1] VARCHAR(20),
[COL2] VARCHAR(10),
PRIMARY KEY ([COL1],[COL2])
)
INSERT INTO [Table]
SELECT TOP 100 'CONST_VALUE', CONCAT('v', ROW_NUMBER() OVER (ORDER BY @@SPID))
FROM sys.all_columns
Запрос 1
SELECT *
FROM [Table]
WHERE(
([COL1] = 'CONST_VALUE' AND [COL2] = 'V1') OR
([COL1] = 'CONST_VALUE' AND [COL2] = 'V1') OR
([COL1] = 'CONST_VALUE' AND [COL2] = 'V4')
)
План выполнения для этого имеет оператор поиска индекса. Просмотр свойств плана показывает, что запрос на самом деле содержит два разных предиката поиска с несколькими столбцами (а не три поиска. Было бы ошибкой дважды выполнять поиск 'V1' и возвращать эти строки дважды, даже если он указан в WHERE
пункт дважды)
Запрос 2
SELECT *
FROM [Table]
WHERE(
([COL1] = 'CONST_VALUE' AND [COL2] = UPPER('v1')) OR
([COL1] = 'CONST_VALUE' AND [COL2] = UPPER('V1')) OR
([COL1] = 'CONST_VALUE' AND [COL2] = UPPER('v2'))
)
Этот план выполнения выглядит многообещающим, но при ближайшем рассмотрении поиск выполняется только по одному столбцу COL1
- поскольку все строки в таблице имеют значение 'CONST_VALUE'
, в этом случае поиск ничего не достигает, и вся работа выполняется с остаточным предикатом.
Запрос 3
SELECT *
FROM [Table] WITH (FORCESEEK)
WHERE(
([COL1] = 'CONST_VALUE' AND [COL2] = UPPER('v1')) OR
([COL1] = 'CONST_VALUE' AND [COL2] = UPPER('V1')) OR
([COL1] = 'CONST_VALUE' AND [COL2] = UPPER('v2'))
)
Это то же самое, что и предыдущий, но с добавленной подсказкой FORCESEEK
. По какой-то причине результаты UPPER
не постоянно сгибаются во время компиляции, поэтому в план добавляются дополнительные операторы для оценки UPPER
, а затем свертываются идентичные результаты для выполнения двух необходимых многостолбительных поисков индекса.
Запрос 4
SELECT *
FROM [Table]
WHERE(
([COL1] = UPPER('CONST_VALUE') AND [COL2] = UPPER('v1')) OR
([COL1] = UPPER('CONST_VALUE') AND [COL2] = UPPER('V1')) OR
([COL1] = UPPER('CONST_VALUE') AND [COL2] = UPPER('v2'))
)
Теперь SQL Сервер сдается и просто сканирует
Query 5
SELECT *
FROM [Table]
WHERE [COL1] = UPPER('CONST_VALUE') AND
(
[COL2] = UPPER('v1') OR
[COL2] = UPPER('V1') OR
[COL2] = UPPER('v2')
)
Это переписывание дает тот же план выполнения, что и Query 2 - с поиском на Col1
и остаточный предикат на Col2
, это бесполезно с моими примерами, но было бы более реалистично c случаев.
Запрос 6
SELECT *
FROM sys.all_objects
where 'v1' <> 'v1'
SQL Сервер обнаруживает противоречие во время компиляции и дает очень простой план
Запрос 7
SELECT *
FROM sys.all_objects
where UPPER('v1') <> UPPER('v1')
Несмотря на то, что выражения являются детерминированными c и имеют точно такие же входные значения, обнаружение противоречий не происходит