Искать предикат не используя все доступные столбцы

8

У меня странная проблема с компиляцией запросов, которую сложно воспроизвести. Это происходит только при высокой нагрузке и не может быть легко повторено.

  • Есть таблица T с колонками A, B, C, D.
  • Существует неуникальный кластерный индекс на T (A, B, C, D).
  • Есть запрос SELECT * ОТ Т, ГДЕ A = @ P1 И B = @ P2 И (C = @ P3 ИЛИ C = @ P4) И D = @ P5. Условие поиска находится во всех столбцах кластеризованного индекса, у 3-го столбца есть ИЛИ.

Проблема в том, что план запроса для этого запроса имеет Предикат поиска только для A и B! Предикат для C и D является обычным предикатом, поэтому это означает, что дерево поиска в столбцах C и D не используется.

Типы данных для всех параметров соответствуют типам данных столбца.

Кто-нибудь может дать какие-нибудь подсказки о том, почему это может происходить? Версия SQL 2008 R2 (SP1) - 10.50.2789.0 (X64)


источник
Вы когда-нибудь получали план параметризованного запроса, который выполняет поиск равенства по всем 4 столбцам? Если да, то вы используете OPTION (RECOMPILE)?
Мартин Смит

Ответы:

8

Для параметризованного запроса он не может просто выполнить два поиска

WHERE A=@P1 AND B=@P2 AND C=@P3 AND D=@P5 

а также

WHERE A=@P1 AND B=@P2 AND C=@P4 AND D=@P5 

Потому @P3 = @P4что, если это неправильно вернет дубликаты строк. Так что для этого потребуется оператор, который сначала удалит дубликаты из них.

Из быстрого теста этот конец, кажется, зависит от размера стола, получите вы это или нет. В приведенном ниже тесте 245/ 246rows - это точка отсечения между планами (это также была точка отсечения между индексом, размещающим все на одной странице, и он стал 2 листами листа и корневой страницей).

CREATE TABLE T(A INT,B INT,C INT,D INT)

INSERT INTO T
SELECT TOP (245) 1,2,3,5
FROM master..spt_values v1

CREATE CLUSTERED INDEX IX ON T(A, B, C, D)

SELECT index_level,page_count, record_count
FROM sys.dm_db_index_physical_stats(db_id(),object_id('T'),1,NULL, 'DETAILED')

DECLARE @C1 INT = 3,
        @C2 INT = 4

 SELECT * FROM T WHERE A=1 AND B=2 AND (C=@C1 OR C=@C2) AND D=5

 DROP TABLE T

1 страница / 245 строк

Этот план имеет поиск A=1 AND B=2с остаточным предикатом на(C=@C1 OR C=@C2) AND D=5

План 1

2 листа страниц / 246 строк

План 2

Во втором плане дополнительные операторы несут ответственность за удаление любых дубликатов с @C1,@C2первого до выполнения поиска (ов).

Поиск во втором плане - это поиск диапазона между остаточным предикатом A=1 AND B=2 AND C > Expr1010и A=1 AND B=2 AND C < Expr1011с остаточным предикатом D=5. Это все еще не поиск равенства во всех 4 колонках. Более подробную информацию о дополнительных операторах плана можно найти здесь .

Добавление OPTION (RECOMPILE)действительно позволяет ему проверять значения параметров на наличие дубликатов во время компиляции и создает план с двумя поисками равенства.

Вы также можете достичь этого с

;WITH CTE
     AS (SELECT DISTINCT ( C )
         FROM   (VALUES (@C1),
                        (@C2)) V(C))
SELECT CA.*
FROM   CTE
       CROSS APPLY (SELECT *
                    FROM   T
                    WHERE A=1 AND B=2 AND D=5  AND C = CTE.C) CA

План 3

Но на самом деле в этом тестовом примере это, скорее всего, будет контрпродуктивно, так как наличие двух поисков в индексе одной страницы вместо одного увеличивает логический ввод-вывод.

Мартин Смит
источник
1
Прошло некоторое тестирование по этому вопросу вчера вечером, прежде чем вы добавили свой первый комментарий. Я дошел до того, что увидел поведение, но не понял, что его вызвало (@ P3 = @ P4), поэтому +1 за это (уже сделал). Я думаю, select .. union select ...что также даст вам два поиска плюс дополнительный шаг удаления дубликатов из результата.
Микаэль Эрикссон
1
@MikaelEriksson - Но SELECT * FROM T WHERE A=1 AND B=2 AND C=@C1 AND D=5 UNION SELECT * FROM T WHERE A=1 AND B=2 AND C=@C2 AND D=5может неправильно удалить дубликаты, которые должны быть возвращены. В моем примере данных, где я лениво заполнял все с одним и тем же значением, он возвращал бы не 1 строку256
Martin Smith
2
О да. И OP даже указал, что ключ не является уникальным. Удачи мне, я не пытался ответить на этот вопрос :).
Микаэль Эрикссон
4

Полностью согласен с анализом Мартина. Этот запрос не может произвести поиск по всем 4 столбцам из-за предиката ИЛИ, если (возможно) с OPTION (RECOMPILE). Я предполагаю, что это запрос «иголка в стоге сена», возможно, вам не нужны дополнительные издержки.

Как насчет этого:

IF @P3=@P4
    SELECT * FROM T WHERE A=@P1 AND B=@P2 AND C=@P3 AND D=@P5.
ELSE
    SELECT * FROM T WHERE A=@P1 AND B=@P2 AND C=@P3 AND D=@P5.
    UNION ALL
    SELECT * FROM T WHERE A=@P1 AND B=@P2 AND C=@P4 AND D=@P5.

Я не проверял это, но остальная часть должна дать 2 поиска по всем 4 значениям и дешевый союз через конкатенацию. В случае, когда оба поиска заканчиваются на одной странице, я не думаю, что дополнительное чтение логической страницы добавит много времени. Однако, в зависимости от ваших данных, два поиска могут быть на разных страницах.

Влад Г.
источник