Jag skulle skriva om testet som
IF CASE
WHEN EXISTS (SELECT ...) THEN CASE
WHEN EXISTS (SELECT ...) THEN 1
END
END = 1
Detta garanterar kortslutning som beskrivs här men betyder att du måste välja den billigaste att utvärdera i förväg istället för att överlåta det till optimeraren.
I mina extremt begränsade tester nedan verkade följande stämma när jag testade
1. EXISTS AND EXISTS
EXISTS AND EXISTS
versionen verkar mest problematisk. Detta kedjer ihop några yttre halvfogar. I inget av fallen ändrade den ordningen på testerna för att försöka göra det billigare först (en fråga som diskuterades i andra halvan av detta blogginlägg). I IF ...
version skulle det inte ha gjort någon skillnad om det hade gjort det eftersom det inte kortslutit. Men när detta kombinerade predikat sätts i en WHERE
klausul planen ändras och den gör kortslutning så att omarrangemang kunde ha varit fördelaktigt.
/*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
*/
Planerna för alla dessa verkar mycket lika. Anledningen till skillnaden i beteende mellan SELECT 1 WHERE ...
version och IF ...
versionen är att för den förra om villkoret är falskt så är det korrekta beteendet att inte returnera något resultat så det bara kedjar OUTER SEMI JOINS
och om en är falsk så går noll rader vidare till nästa.
Men IF
version alltid måste returnera ett resultat på 1 eller noll. Den här planen använder en sondkolumn i dess yttre kopplingar och ställer in detta på falskt om EXISTS
testet är inte godkänt (snarare än att bara kassera raden). Det betyder att det alltid finns en rad som matas in i nästa Join och den exekveras alltid.
CASE
versionen har en mycket liknande plan men den använder en PASSTHRU
predikat som den använder för att hoppa över körningen av JOIN om föregående THEN
villkoret var inte uppfyllt. Jag är inte säker på varför man kombinerade AND
s skulle inte använda samma tillvägagångssätt.
2. EXISTS OR EXISTS
EXISTS OR EXISTS
versionen använde en sammanlänkning (UNION ALL
)-operator som den inre ingången till en yttre semi-join. Detta arrangemang innebär att den kan sluta begära rader från insidan så snart den första returneras (dvs. den kan effektivt kortsluta). Alla fyra frågorna slutade med samma plan där det billigare predikatet utvärderades 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. Lägger till en ELSE
Det föll mig in att pröva De Morgans lag för att konvertera AND
till OR
och se om det gjorde någon skillnad. Konvertering av den första frågan ger
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å detta gör fortfarande ingen skillnad för kortslutningsbeteendet. Men om du tar bort NOT
och vänd ordningen på IF ... ELSE
villkor 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
*/