Условный поток SQL Server
Если я напишу два SELECT
заявления в IF EXISTS
состояние с AND
между этими запросами выбора, выполняются ли оба запроса, даже если первый SELECT
возвращает ложь?
IF EXISTS (SELECT....) AND EXISTS(SELECT ....)
BEGIN
END
Выполняет ли SQL Server Engine оба оператора SQL в этом сценарии?
Спасибо криш
7 ответов
Я бы переписал тест как
IF CASE
WHEN EXISTS (SELECT ...) THEN CASE
WHEN EXISTS (SELECT ...) THEN 1
END
END = 1
Это гарантирует короткое замыкание, как описано здесь, но означает, что вам нужно выбрать самый дешевый для оценки, а не оставлять его на усмотрение оптимизатора.
В моих чрезвычайно ограниченных тестах ниже, казалось, справедливо следующее
1. EXISTS AND EXISTS
EXISTS AND EXISTS
версия кажется самой проблемной. Это объединяет некоторые внешние полусоединения. Ни в одном из случаев он не изменил порядок тестов, чтобы сначала попытаться сделать более дешевый ( проблема, обсуждаемая во второй половине этого сообщения в блоге). в IF ...
версия не имела бы никакого значения, если бы не имела короткого замыкания. Однако, когда этот комбинированный предикат помещается в WHERE
оговорка о том, что план меняется, и он делает короткое замыкание, чтобы перестановка могла быть полезной.
/*All tests are testing "If False And False"*/
IF EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
AND EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)
PRINT 'Y'
/*
Table 'spt_values'. Scan count 1, logical reads 9
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
IF EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)
AND EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
PRINT 'Y'
/*
Table 'spt_monitor'. Scan count 1, logical reads 1
Table 'spt_values'. Scan count 1, logical reads 9
*/
SELECT 1
WHERE EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
AND EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
SELECT 1
WHERE EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)
AND EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_values'. Scan count 1, logical reads 9
*/
Планы на все это выглядят очень похоже. Причина различий в поведении между SELECT 1 WHERE ...
версия и IF ...
версия заключается в том, что для первого, если условие ложно, то правильное поведение не должно возвращать результат, поэтому он просто цепочка OUTER SEMI JOINS
и если один равен false, то нулевые строки переносятся на следующий.
Тем не менее IF
Версия всегда должна возвращать результат 1 или ноль. Этот план использует столбец зонда в своих внешних соединениях и устанавливает значение false, если EXISTS
тест не пройден (а не просто отбрасывается строка). Это означает, что в следующее соединение всегда подается 1 строка, и оно всегда выполняется.
CASE
версия имеет очень похожий план, но использует PASSTHRU
предикат, который он использует, чтобы пропустить выполнение JOIN, если предыдущий THEN
условие не было выполнено. Я не уверен, почему в сочетании AND
не будет использовать тот же подход.
2. EXISTS OR EXISTS
EXISTS OR EXISTS
версия использовала конкатенацию (UNION ALL
) оператор как внутренний вход для внешнего полусоединения. Такое расположение означает, что он может прекратить запрашивать строки с внутренней стороны, как только будет возвращен первый (т. Е. Он может эффективно замкнуть цепь). Все 4 запроса закончились тем же планом, в котором сначала был оценен более дешевый предикат.
/*All tests are testing "If True Or True"*/
IF EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=1)
OR EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1)
PRINT 'Y'
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
IF EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1)
OR EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)= 1)
PRINT 'Y'
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
SELECT 1
WHERE EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)= 1)
OR EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1)
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
SELECT 1
WHERE EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)<>1)
OR EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=1)
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
3. Добавление ELSE
Мне пришло в голову попробовать закон де Моргана, чтобы преобразовать AND
в OR
и посмотрим, имеет ли это какое-то значение. Преобразование первого запроса дает
IF NOT ((NOT EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
OR NOT EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1)))
PRINT 'Y'
ELSE
PRINT 'N'
/*
Table 'spt_monitor'. Scan count 1, logical reads 1
Table 'spt_values'. Scan count 1, logical reads 9
*/
Так что это все равно не имеет никакого значения для поведения короткого замыкания. Однако если вы удалите NOT
и обратный порядок IF ... ELSE
условия это сейчас делает короткое замыкание!
IF (NOT EXISTS(SELECT COUNT(*) FROM master..spt_monitor HAVING COUNT(*)=2)
OR NOT EXISTS (SELECT COUNT(*) FROM master..spt_values HAVING COUNT(*)=1))
PRINT 'N'
ELSE
PRINT 'Y'
/*
Table 'Worktable'. Scan count 0, logical reads 0
Table 'spt_monitor'. Scan count 1, logical reads 1
*/
Я полагаю, что вы можете положиться на короткое замыкание операторов IF в большинстве, если не во всех, современных языках. Вы можете попробовать протестировать, поставив истинное условие первым и заменив второе условие на 1/0
что даст вам ошибку деления на ноль, если короткое замыкание не произойдет, например:
IF 1>0 OR 1/0 BEGIN
PRINT 'Short Circuited'
END
Если вы не доверяете этому, вы всегда можете переписать свой запрос, чтобы сделать это:
IF EXISTS(SELECT...) BEGIN
IF EXISTS(SELECT...) BEGIN
...
END
END
Если я выполню запрос с AND, даже тогда, обе таблицы будут доступны
УСТАНОВИТЬ СТАТИСТИКУ IO ВКЛ, ЕСЛИ EXISTS (SELECT * from master..spt_values где [name] = 'rpcc') и EXISTS(SELECT * from master..spt_monitor где pack_sent = 5235252) ПЕЧАТЬ 'Y'
Таблица "spt_monitor". Сканирование 1, логическое чтение 1, физическое чтение 0, чтение с опережением 0, логическое чтение с бита 0, физическое чтение с бита 0, чтение с опережением чтения 0. Таблица "spt_values". Сканирование 1, логическое чтение 17, физическое чтение 0, чтение с опережением 0, логическое чтение с 0, физическое чтение с 0, чтение с опережением 0.
Я беру следующие цитаты из следующей записи блога на sqlteam:
Как SQL Server закорачивает ГДЕ оценку состояния
Это происходит, когда хочется, но не так, как вы сразу думаете.
Как разработчик, вы должны знать, что SQL Server не замыкает накоротко, как это делается в других языках программирования, и вы ничего не можете сделать, чтобы его заставить.
Для получения дополнительной информации проверьте первую ссылку в приведенной выше записи блога, которая ведет к другому блогу:
SQL Server короткое замыкание?
Окончательный вердикт? Ну, на самом деле у меня его еще нет, но, вероятно, можно с уверенностью сказать, что единственный раз, когда вы можете обеспечить конкретное короткое замыкание, это когда вы выражаете несколько условий WHEN в выражении CASE. Со стандартными логическими выражениями оптимизатор будет перемещать вещи по своему усмотрению на основе таблиц, индексов и данных, которые вы запрашиваете.
Было интересное наблюдение. У меня есть две таблицы TBL и TBLB. У tbla есть первичный ключ (idvalue), который используется в качестве внешнего ключа в tblb. У обоих есть строка с idvalue = 1, но нет строки с idvalue -1. Теперь под запросом используется только одна таблица
select 1
where exists
(select 1 from tbla where idvalue = -1)
and exists (select 1 from tblb where idvalue= 1)
дает
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tbla'. Scan count 0, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Это очевидно, потому что оптимизатор знает, что, поскольку существует отношение первичного ключа к внешнему ключу, поэтому, если значение отсутствует в tbla, оно никогда не может присутствовать в tblb. Таким образом, оптимизатор примет решение о времени выполнения, которое не требует поиска по tblb.
Однако, если я напишу запрос как
select 1
where exists
(select 1 from tbla where idvalue = 1)
and exists (select 1 from tblb where idvalue= -1)
тогда обе таблицы доступны. Это довольно очевидно, так как здесь оптимизатор знает, что он должен проверить в обоих местах, чтобы убедиться, что условие AND выполнено.
Тем не менее, в обоих случаях фактический план выполнения показывает поиски как в tbla, так и в tblb. Это кажется странным для меня. Есть мысли по этому поводу?
Нету.
Я только что проверил в SQL Server 2008, и если первая оценка не удается, он сразу пропускает IF
блок.
Это очень легко проверить.
Для вашей первой оценки сделайте что-то вроде IF 1=0
и на секунду сделайте что-нибудь, а затем покажите фактический план. В моем случае это только сканирование констант, чтобы оценить эти константы.
Вы можете предотвратить второе сканирование, выполнив это:
declare @test bit
select @test = case when exists(select 1...) then 1 else 0 end
if @test = 1
begin
--1st test passed
select @test = case when exists(select 2...) then 1 else 0 end
end
if @test = 1
begin
print 'both exists passed'
end