Jeg ville omskrive testen som
IF CASE
WHEN EXISTS (SELECT ...) THEN CASE
WHEN EXISTS (SELECT ...) THEN 1
END
END = 1
Dette garanterer kortslutning som beskrevet her, men det betyder, at du skal vælge den billigste for at evaluere på forhånd i stedet for at overlade det til optimizeren.
I mine ekstremt begrænsede test nedenfor syntes følgende at holde stik, når jeg testede
1. EXISTS AND EXISTS
EXISTS AND EXISTS
version virker mest problematisk. Dette kæder nogle ydre semi-sammenføjninger sammen. I ingen af tilfældene omarrangerede den rækkefølgen af testene for at prøve at lave den billigere først (et problem diskuteret i anden halvdel af dette blogindlæg). I IF ...
version, ville det ikke have gjort nogen forskel, hvis det havde gjort det, da det ikke kortsluttede. Men når dette kombinerede prædikat er sat i en WHERE
klausul planen ændres, og det gør det kortslutning, så omarrangering kunne have været gavnlig.
/*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
*/
Planerne for alle disse ser meget ens ud. Årsagen til forskellen i adfærd mellem SELECT 1 WHERE ...
version og IF ...
version er, at for den tidligere, hvis betingelsen er falsk, så er den korrekte adfærd at returnere intet resultat, så det kæder bare OUTER SEMI JOINS
og hvis en er falsk, så går nul rækker videre til den næste.
Men IF
version altid skal returnere et resultat på 1 eller nul. Denne plan bruger en probesøjle i dens ydre joinforbindelser og indstiller denne til falsk, hvis EXISTS
prøven er ikke bestået (i stedet for blot at kassere rækken). Det betyder, at der altid er 1 række, der føres ind i den næste Join, og den bliver altid udført.
CASE
version har en meget lignende plan, men den bruger en PASSTHRU
prædikat, som den bruger til at springe udførelse af JOIN over, hvis den forrige THEN
betingelsen ikke var opfyldt. Jeg er ikke sikker på, hvorfor kombineret AND
s ville ikke bruge den samme tilgang.
2. EXISTS OR EXISTS
EXISTS OR EXISTS
version brugte en sammenkædning (UNION ALL
) operatør som det indre input til en ydre semi join. Dette arrangement betyder, at den kan stoppe med at anmode om rækker fra indersiden, så snart den første er returneret (dvs. den kan effektivt kortslutte). Alle 4 forespørgsler endte med den samme plan, hvor det billigere prædikat blev evalueret først.
/*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. Tilføjelse af en ELSE
Det faldt mig ind at prøve De Morgans lov om at konvertere AND
til OR
og se om det gjorde nogen forskel. Konvertering af den første forespørgsel giver
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
*/
Så dette gør stadig ingen forskel for kortslutningsadfærden. Men hvis du fjerner NOT
og omvendt rækkefølgen af IF ... ELSE
forhold, det nu gør kortslutning!
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
*/