Riscriverei il test come
IF CASE
WHEN EXISTS (SELECT ...) THEN CASE
WHEN EXISTS (SELECT ...) THEN 1
END
END = 1
Ciò garantisce il cortocircuito come descritto qui, ma significa che devi selezionare quello più economico da valutare in anticipo piuttosto che lasciarlo all'ottimizzatore.
Nei miei test estremamente limitati di seguito, sembrava che fosse vero durante il test
1. EXISTS AND EXISTS
Il EXISTS AND EXISTS
la versione sembra più problematica. Questo incatena alcuni semigiunti esterni. In nessuno dei casi ha riorganizzato l'ordine dei test per provare a fare prima quello più economico (un problema discusso nella seconda metà di questo post sul blog). Nel IF ...
versione non avrebbe fatto alcuna differenza se l'avesse fatto in quanto non andava in cortocircuito. Tuttavia, quando questo predicato combinato viene inserito in un WHERE
clausola il piano cambia e fa cortocircuito in modo che il riarrangiamento avrebbe potuto essere vantaggioso.
/*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
*/
I piani per tutti questi sembrano molto simili. Il motivo della differenza di comportamento tra SELECT 1 WHERE ...
versione e il IF ...
la versione è che per la prima se la condizione è falsa, il comportamento corretto è di non restituire alcun risultato, quindi si limita a concatenare i OUTER SEMI JOINS
e se uno è falso, zero righe portano avanti a quella successiva.
Tuttavia il IF
versione sempre deve restituire un risultato pari a 1 o zero. Questo piano utilizza una colonna probe nei suoi join esterni e lo imposta su false se EXISTS
il test non viene superato (piuttosto che scartare semplicemente la riga). Ciò significa che c'è sempre 1 riga che alimenta il join successivo e viene sempre eseguito.
Il CASE
la versione ha un piano molto simile ma usa un PASSTHRU
predicato che usa per saltare l'esecuzione del JOIN se il precedente THEN
condizione non era soddisfatta. Non so perché combinare AND
s non userebbe lo stesso approccio.
2. EXISTS OR EXISTS
Il EXISTS OR EXISTS
versione utilizzava una concatenazione (UNION ALL
) come input interno per un semi join esterno. Questa disposizione significa che può interrompere la richiesta di righe dal lato interno non appena viene restituita la prima (cioè può effettivamente cortocircuitare) Tutte e 4 le query sono finite con lo stesso piano in cui è stato valutato per primo il predicato più economico.
/*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. Aggiunta di un ELSE
Mi è venuto in mente di provare la legge di De Morgan per convertire AND
a OR
e vedi se questo ha fatto la differenza. La conversione della prima query dà
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
*/
Quindi questo ancora non fa alcuna differenza per il comportamento in cortocircuito. Tuttavia, se rimuovi il NOT
e invertire l'ordine di IF ... ELSE
condizioni che ora fa cortocircuito!
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
*/