Hur matematikkunskaper kan hjälpa till med optimering


Idag fick jag tillfället att hjälpa en kollega med att optimera en fråga för en kunds räkning. Originalfrågan såg i princip ut så här

— Original
SELECT      t.*
FROM        dbo.Table1 AS t
CROSS JOIN  (
                SELECT  ChildID
               
FROM    dbo.Table1
           
) AS w
WHERE       (
                t.ParentID = w.ChildID AND t.ParentID <> t.ChildID
               
OR
               
t.ChildID = t.ParentID AND t.ChildID = w.ChildID
           
)
           
AND t.CategoryID IN (’11’, ’22’, ’33’)

 

Det som jag reagerade först på var användandet av OR i WHERE-satsen. Det gör det mycket svårare för Query Optimizer att välja ett bra index och att skapa en bra exekveringsplan. När jag sedan tittat lite närmare på den för att se vad som egentligen ska hända, vilken affärsregel som ska uppfyllas, ser jag att uttrycket är överflödigt.

Låt oss börja med uttrycket efter OR.

t.ChildID = t.ParentID
t.ChildID = w.ChildID

Av detta kan vi sluta oss till att även uttrycket t.ParentID = w.ChildID är sant. Dessutom är det sant att om t.ChildID = t.ParentID så är det dessutom sant att t.ParentID = t.ChildID. Låt oss skriva om uttrycket med vår härledning och dessutom byta plats på uttrycket efter OR.

— Härledning
SELECT      t.*
FROM        dbo.Table1 AS t
CROSS JOIN  (
                SELECT  ChildID
               
FROM    dbo.Table1
           
) AS w
WHERE       (
                t.ParentID = w.ChildID AND t.ParentID <> t.ChildID
               
OR
                t.ParentID = w.ChildID
AND t.ParentID = t.ChildID
           
)
           
AND t.CategoriID IN (’11’, ’22’, ’33’)

Vad vi nu kan se är att vi har två exklusiva uttryck i vår OR. I det ena fallet ska t.ParentID = t.ChildID och i det andra utttycket ska t.ParentID <> t.ChildID.
Av detta vet vi att alla rader ska med, för antingen så har de två kolumnerna lika värde eller så har de det inte. Vi behöver heller inte heller ta hänsyn till NULL här då de båda kolumnerna inte tillåter NULL. Om vi plockar bort det onödiga uttrycket från frågan får vi följande query.

— Bortplockning
SELECT      t.*
FROM        dbo.Table1 AS t
CROSS JOIN  (
                SELECT  ChildID
               
FROM    dbo.Table1
           
) AS w
WHERE       (
                t.ParentID = w.ChilID
               
OR
               
t.ParentID = w.ChildID
           
)
           
AND t.CategoryID IN (’11’, ’22’, ’33’)

Nu börjar det likna något! Ett uttryck ska vara sant eller så ska samma uttryck igen vara sant. Det är en överflödig kontroll, så det slutade här med att jag skrev om frågan så den såg ut så här istället.

— Optimerad
SELECT        t.*
FROM          dbo.Table1 AS t
INNER JOIN    dbo.Table1 AS w ON w.ChildID = t.ParentID
WHERE         t.CategoryID IN (’11’, ’22’, ’33’)


Vad gjorde det då för prestandan? Med hjälp av SET STATISTICS TIME, IO ON fick jag följande resultat.

— Original
(391 row(s) affected)
Table ‘Worktable’. Scan count  8, logical reads 151599, physical reads 0.
Table ‘Table1’.    Scan count 17, logical reads   1466, physical reads 0.
Table ‘Worktable’. Scan count  0, logical reads      0.

SQL Server Execution Times:
   CPU time = 11389 ms, elapsed time = 1873 ms.

— Omskrivning
(391 row(s) affected)
Table ‘Worktable’. Scan count  0, logical reads      0, physical reads 0.
Table ‘Table1’.    Scan count  2, logical reads   1256, physical reads 0.

SQL Server Execution Times:
   CPU time =   733 ms, elapsed time =  732 ms.

Detta lilla matematiska knep gjorde att omskrivningen använder 15 ggr färre CPU samt slutförs 2,5 ggr snabbare än tidigare. När jag tittade närmare i exekveringsplanen såg jag att omskrivningen bara behöver 1 core medan originalfrågan la beslag på alla 16 cores.

Om det nu varit så att kolumnerna tillåtit NULL, hade jag varit tvungen att lägga till en extra kontroll som den här för att efterlikna originalfrågan till 100%.

— Optimerad med NULL
SELECT        t.*
FROM          dbo.Table1 AS t
INNER JOIN    dbo.Table1 AS w ON w.ChildID = t.ParentID
WHERE         t.CategoryID IN (’11’, ’22’, ’33’)
              AND t.ChildID IS NOT NULL

Have any Question or Comment?

Leave a Reply