Я исследовал что-то еще, когда наткнулся на эту вещь. Я генерировал тестовые таблицы с некоторыми данными и выполнял разные запросы, чтобы выяснить, как разные способы написания запросов влияют на план выполнения. Вот скрипт, который я использовал для генерации случайных тестовых данных:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID('t') AND type in (N'U'))
DROP TABLE t
GO
CREATE TABLE t
(
c1 int IDENTITY(1,1) NOT NULL
,c2 int NULL
)
GO
insert into t
select top 1000000 a from
(select t1.number*2048 + t2.number a, newid() b
from [master]..spt_values t1
cross join [master]..spt_values t2
where t1.[type] = 'P' and t2.[type] = 'P') a
order by b
GO
update t set c2 = null
where c2 < 2048 * 2048 / 10
GO
CREATE CLUSTERED INDEX pk ON [t] (c1)
GO
CREATE NONCLUSTERED INDEX i ON t (c2)
GO
Теперь, учитывая эти данные, я вызвал следующий запрос:
select *
from t
where
c2 < 1048576
or c2 is null
;
К моему большому удивлению, план выполнения, который был сгенерирован для этого запроса, был следующим . (Извините за внешнюю ссылку, она слишком велика, чтобы поместиться здесь).
Может кто-нибудь объяснить мне, что случилось со всеми этими « постоянными сканированиями » и «компьютерными скалярами »? Что происходит?
|--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1010], [Expr1011], [Expr1012]))
|--Merge Interval
| |--Sort(TOP 2, ORDER BY:([Expr1013] DESC, [Expr1014] ASC, [Expr1010] ASC, [Expr1015] DESC))
| |--Compute Scalar(DEFINE:([Expr1013]=((4)&[Expr1012]) = (4) AND NULL = [Expr1010], [Expr1014]=(4)&[Expr1012], [Expr1015]=(16)&[Expr1012]))
| |--Concatenation
| |--Compute Scalar(DEFINE:([Expr1005]=NULL, [Expr1006]=NULL, [Expr1004]=(60)))
| | |--Constant Scan
| |--Compute Scalar(DEFINE:([Expr1008]=NULL, [Expr1009]=(1048576), [Expr1007]=(10)))
| |--Constant Scan
|--Index Seek(OBJECT:([t].[i]), SEEK:([t].[c2] > [Expr1010] AND [t].[c2] < [Expr1011]) ORDERED FORWARD)
источник
62
что для сравнения равенства. Я думаю,60
должно означать, что вместо того,> AND <
что показано в плане, который вы на самом деле получаете,>= AND <=
если только это не явныйIS NULL
флаг, может быть (?) Или, может быть, бит2
указывает на что-то еще не связанное и60
все еще равенство, как когда я делаюset ansi_nulls off
и изменяю его,c2 = null
он все еще остается в60
Постоянное сканирование - это способ для SQL Server создать сегмент, в который он будет помещать что-то позднее в план выполнения. Я разместил более подробное объяснение этого здесь . Чтобы понять, для чего нужно постоянное сканирование, вам нужно заглянуть в план. В данном случае это операторы Compute Scalar, которые используются для заполнения пространства, созданного постоянным сканированием.
Операторы Compute Scalar загружаются с NULL и значением 1045876, поэтому они явно будут использоваться с Loop Join в целях фильтрации данных.
Действительно крутая часть в том, что этот план тривиален. Это означает, что он прошел минимальный процесс оптимизации. Все операции ведут к интервалу слияния. Это используется для создания минимального набора операторов сравнения для поиска по индексу ( подробности здесь ).
Вся идея состоит в том, чтобы избавиться от перекрывающихся значений, чтобы затем можно было извлечь данные с минимальными проходами. Хотя он все еще использует операцию цикла, вы заметите, что цикл выполняется ровно один раз, то есть это фактически сканирование.
ADDENDUM: последнее предложение не подходит. Было два поиска. Я неправильно понял план. Остальные понятия одинаковы, а цель, минимальные пропуски, одинакова.
источник