Вычисляемый индекс столбца не используется

14

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

Кажется, есть и другие подобные вопросы, но ни один из них не сфокусирован на том, почему индекс не будет использоваться.

Тестовая таблица:

CREATE TABLE dbo.Diffs
    (
    Id int NOT NULL IDENTITY (1, 1),
    DataA int NULL,
    DataB int NULL,
    DiffPersisted  AS isnull(convert(bit, case when [DataA] is null and [DataB] is not null then 1 when [DataA] <> [DataB] then 1 else 0 end), 0) PERSISTED ,
    DiffComp  AS isnull(convert(bit, case when [DataA] is null and [DataB] is not null then 1 when [DataA] <> [DataB] then 1 else 0 end), 0),
    DiffStatic bit not null,
    Primary Key (Id)
    )

create index ix_DiffPersisted on Diffs (DiffPersisted)
create index ix_DiffComp on Diffs (DiffComp)
create index ix_DiffStatic on Diffs (DiffStatic)

И запрос:

select Id from Diffs where DiffPersisted = 1
select Id from Diffs where DiffComp = 1
select Id from Diffs where DiffStatic = 1

И итоговые планы выполнения: План выполнения

Дэвид Фавр
источник

Ответы:

10

Попробуйте COALESCEвместо ISNULL. При этом ISNULLSQL Server, похоже, не способен выдвинуть предикат к более узкому индексу, и поэтому вынужден сканировать кластеризованную информацию, чтобы найти информацию.

CREATE TABLE dbo.Diffs
    (
    Id int NOT NULL IDENTITY (1, 1),
    DataA int NULL,
    DataB int NULL,
    DiffPersisted  AS COALESCE(convert(bit, case when [DataA] is null 
      and [DataB] is not null then 1 when [DataA] <> [DataB] 
      then 1 else 0 end), 0) PERSISTED ,
    DiffComp  AS COALESCE(convert(bit, case when [DataA] is null 
      and [DataB] is not null then 1 when [DataA] <> [DataB] 
      then 1 else 0 end), 0),
    DiffStatic bit not null,
    Primary Key (Id)
    );

Тем не менее, если вы придерживаетесь статического столбца, отфильтрованный индекс может иметь больше смысла и будет иметь более низкие затраты ввода-вывода (все в зависимости от того, сколько строк обычно соответствует предикату фильтра), например:

CREATE INDEX ix_DiffStaticFiltered 
  ON dbo.Diffs(DiffStatic)
  WHERE DiffStatic = 1;
Аарон Бертран
источник
Очень интересно, не подумал бы об этом. Похоже, что COALESCEна этом этапе вы можете просто избавиться от ; Я полагаю, что CASEоператор уже гарантированно возвращал 0или 1, но ISNULLприсутствовал только для того, чтобы SQL Server мог дать необнуляемое значение BITдля вычисляемого столбца. Тем не менее, по- COALESCEпрежнему будет обнуляться столбец Таким образом, единственное влияние этого изменения, с или без COALESCE, состоит в том, что вычисляемый столбец теперь обнуляем, но можно использовать поиск по индексу.
Джефф Паттерсон
@ Да, это правда. Но в этом случае, поскольку мы знаем, что по определению вычисляемого столбца NULL на самом деле не является возможным выводом, это действительно имеет значение , только если мы используем эту таблицу в качестве источника SELECT INTO.
Аарон Бертран
Это удивительная информация - спасибо! Моя конечная цель состоит в том, чтобы столбцы DataA и DataB использовались как «грязные» uuids для асинхронного обновления денормализованных столбцов в записи, поэтому не должно быть слишком много, где флаг Diff равен 1. Если я использую статический поле, тогда я думал о добавлении триггера для мониторинга двух uuids и обновления поля.
Дэвид Фэйвр
Кроме того, как указал @GeoffPatterson, я не могу использовать COALESCE? Зачем мне это держать?
Дэвид Фэйвр
@ Давид Вы, вероятно, можете бросить COALESCE. Я пытался сохранить внешний вид и намерения вашего исходного кода и не тестировал без него, поэтому тестирование будет на вас. (Я также не могу объяснить, почему вы ISNULLтам были .)
Аарон Бертран
5

Это конкретное ограничение логики сопоставления вычисляемых столбцов SQL Server, когда используется самый внешний ISNULLи тип данных столбца bit.

Отчет об ошибке

Чтобы избежать этой проблемы, можно использовать любой из следующих обходных путей:

  1. Не используйте самый внешний ISNULL(единственный способ сделать вычисляемый столбец NOT NULL).
  2. Не используйте bitтип данных в качестве окончательного типа вычисляемого столбца.
  3. Создайте вычисляемый столбец PERSISTEDи включите флаг трассировки 174 .

Детали

Суть проблемы заключается в том, что без флага трассировки 174 все вычисленные ссылки на столбцы в запросе (даже сохраненные) всегда расширяются до базового определения очень рано при компиляции запроса.

Идея расширения заключается в том, что оно может позволить упрощения и переписывания, которые могут работать только с определением, а не только с именем столбца. Например, в запросе могут быть предикаты, ссылающиеся на этот вычисляемый столбец, которые могут сделать часть вычисления избыточной или иным образом более ограниченной.

После раннего упрощения и переписывания компиляция запроса пытается сопоставить выражения в запросе с вычисляемыми столбцами (все вычисляемые столбцы, а не только те, которые изначально были найдены в тексте запроса).

Неизмененные выражения вычисляемого столбца в большинстве случаев соответствуют исходному вычисляемому столбцу без проблем. Кажется, что есть ошибка, когда она специфична для сопоставления выражения bitтипа с самым внешним ISNULL. Совпадение неуспешно в этом конкретном случае, даже если детальное изучение внутренних органов показывает, что оно должно быть успешным.

Пол Уайт 9
источник