Нужна помощь в понимании некоторых планов выполнения - PullRequest
0 голосов
/ 07 июня 2018

Предположим, что простая таблица определена как:

CREATE TABLE Table1
(
[ID] [bigint] NOT NULL IDENTITY(1, 1) NOT FOR REPLICATION,
[State] [tinyint] NOT NULL DEFAULT ((0))
)
ALTER TABLE Table1 ADD CONSTRAINT [PK_Table1] PRIMARY KEY CLUSTERED  ([ID])
CREATE NONCLUSTERED INDEX [IX_NC_F_Media_StateNotDeleted] ON Table1 ([State]) WHERE ([State]<>(2))
CREATE NONCLUSTERED INDEX [IX_NC_F_Media_State] ON Table1 ([State]) WHERE ([State]=(0))

Значения вставляются следующим образом:

250000 rows with State = 0
1000 rows with State = 5

И следующие запросы с соответствующими планами выполнения:

declare @mID int = 400000;
select State 
from Table1 
where (ID = @mID and State in (0, 1, 4, 5))
   or (@mID IS NULL and State in (0, 4))

enter image description here

Учитывая, что ID не равно нулю и, следовательно, @mid IS NULL является взаимоисключающим с ID = @mID, я переписал запрос следующим образом:

declare @mID int = 400000;
if @mID is null
begin
  select State 
  from Table1 
  where State in (0, 4)
end
else
begin
  select State 
  from Table1 
  where ID=@mID and State in (0, 1, 4, 5)
end

enter image description here

Вопросы:

  1. Почему разница в плане выполнения между этими двумя случаями?Почему некластеризованный индекс используется только во втором случае?
  2. Хотя во втором случае выполняется поиск по сравнению со сканированием для случая, когда @mID равен нулю, действительно ли это имеет значение?Подсказка показывает, что производительность почти одинакова, и я предполагаю, что это связано с тем, что данные в основном состоят из State = 0 строк.

Ответы [ 3 ]

0 голосов
/ 14 июня 2018

Вот мое понимание проблемы: в первом случае SQL Server должен разработать план запроса, который был бы достаточно хорошим, чтобы удовлетворить, было ли передано значение @mid или нет.

Два условия в первом запросе выиграют от разных индексов, но затем логически потребуется объединение результирующего набора, поскольку это условие ИЛИ.Поэтому он выбирает план, который удовлетворял бы обоим условиям при одном сканировании кластерного индекса.

Во втором случае проблема устранена, и, таким образом, SQL-сервер может использовать разные индексы для отдельных запросов

0 голосов
/ 15 июня 2018

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

Таким образом, план для первого запроса должен иметь такую ​​форму, которая будет работать для любого значения @mID, включая NULL и not-NULL.

Index IX_NC_F_Media_StateNotDeleted может использоваться для поиска значенийдля обеих частей выражения OR, но либо оптимизатор недостаточно умен, чтобы построить план, который выполняет два поиска по этому индексу, а затем объединяет результат, либо оптимизатор решил, что такой план будет более дорогим.

Таким образом, либо оптимизатор не способен видеть здесь , что @mid IS NULL является взаимоисключающим с ID = @mID, либо он решает, что альтернатива будет более дорогой.

Второй запрос с явным IF делает выбор оптимизатора очевидным.


Этот тип запроса называется «всеобъемлющим» или «получающим» запросом.Я рекомендую прочитать отличную статью Эрланда Соммарскога Условия динамического поиска в T-SQL .

Во многих случаях целесообразно добавить OPTION (RECOMPILE) в ваш первый запрос, например так:

declare @mID int = 400000;
select State 
from Table1 
where (ID = @mID and State in (0, 1, 4, 5))
   or (@mID IS NULL and State in (0, 4))
OPTION(RECOMPILE);

SET @mID = NULL;
select State 
from Table1 
where (ID = @mID and State in (0, 1, 4, 5))
   or (@mID IS NULL and State in (0, 4))
OPTION(RECOMPILE);

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

С OPTION(RECOMPILE) оптимизатор знает, что сгенерированный план не будет кэшироваться как обычно, поэтому он принимает фактическийзначения параметров и встраивает их как константы в запрос.Как только они являются константами, здесь , оптимизатор может видеть, что NULL IS NULL всегда истинно (или 400000 IS NULL всегда ложно) и сворачивает / упрощает логическое выражение.Кроме того, оптимизатор может выбрать наиболее подходящие индексы в каждом случае.


Хотя во втором случае выполняется поиск по сравнению со случаем, когда @mID равен нулю, действительно ли это так?есть какая-то разница?Всплывающая подсказка указывает на то, что производительность почти одинакова, и я предполагаю, что это связано с тем, что данные в основном состоят из строк = 0.

В этом случае поиск индекса практически одинаковкак сканирование всей таблицы.Их размеры (в страницах) одинаковы.Если в вашей таблице много строк с State=2, то отфильтрованный индекс будет более эффективным, так как он будет содержать меньше страниц, чем основная таблица.

0 голосов
/ 12 июня 2018

Ваш запрос не использует индекс в первом запросе, поскольку переменная использовалась как часть критериев (@mID IS NULL и State в (0, 4)), в этом случае @mID IS NULL, потому что этостолбец не может быть включен в индекс, и по этой причине sql не может активировать индекс.Во втором запросе, если вы удаляете эту проблему и индекс активирован.

Во втором разделе if, потому что у вас есть кластеризованный индекс, быстрее для sql найти эту конкретную запись, а затем оценить, есть ли у состояния какое-либо из этих значений, чтобы показать результат.

Что-то дополнительное вне рамок вопроса, после некоторого тестирования я нашел что-то интересное.Когда значения состояний стали более сбалансированными, скажем, есть значения для каждой возможной опции от 1 до 5, план выполнения изменяется, и первый запрос становится таким же хорошим, как и опция, используя if.

declare @mID int = 400000;
select State 
from Table1 
where (ID = @mID and State in (0, 1, 4, 5))
   or (@mID IS NULL and State in (0, 4))

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...