forward scan vs. backward scan

Évekkel ezelőtt volt egy érdekes felvetése egy "menő" tanácsadó cégnek (nem, nincs név :)), miszerint mindegy, hogy forward vagy backward scan van egy indexen, mert ugyan olyan jó. Ezzel én vitába szálltam, mert nem feltétlen igaz ez, főleg nem ott, ahol számít a teljesítmény. Részletekbe nem feltétlen megyek bele, de a lényeget megmutatom.

Demó környezet

A példához szükségem van egy relatív nagy táblára, amit most a tempdb-ben hozok létre:

USE [tempdb]
GO

DROP TABLE IF EXISTS [dbo].[T1]
GO
CREATE TABLE [dbo].[T1]
(
    [col1] INT IDENTITY PRIMARY KEY NONCLUSTERED,
    [col2] INT,
    [col3] DATETIME
)
GO

CREATE CLUSTERED INDEX [CI_T1] ON [dbo].[T1] ([col3])
GO

Ebbe a táblába létrehozok 5 millió sort (meg egy kicsit).

INSERT INTO [dbo].[T1]
(
    [col2],
    [col3]
)
SELECT
    CHECKSUM(NEWID()),
    DATEADD(ms,CHECKSUM(NEWID()), CURRENT_TIMESTAMP)
FROM
    master.sys.objects o 
CROSS JOIN
    master.sys.objects o1
GO

INSERT INTO [dbo].[T1]
(
    [col2],
    [col3]
)
SELECT TOP 5000000
    CHECKSUM(NEWID()),
        DATEADD(ms,CHECKSUM(NEWID()), CURRENT_TIMESTAMP)
FROM
    [dbo].[T1] a 
CROSS JOIN
    [dbo].[T1] b
GO

Forward scan

Lássunk egy egyszerű lekérdezést a csodálatos T1 táblára:

SELECT *
FROM   
    [dbo].[T1]
WHERE  
    [col3] BETWEEN '20170101' AND '20180101'
AND 
    [col2] BETWEEN 300000000 AND 699999999
ORDER BY [col3] ASC;

Ugye nem is bonyolult. Vajon mi a futási terv?

Nahát, ez egy parallel futási terv. Amit még érdemes megnézni az az Index Seek tulajdonságai:

Látható, hogy itt forward scan van, illetve parallel futási terv van, azaz több CPU-t is tudunk használni. Ez utóbbi az érdekes, és ezen volt a "vita". 

Backward scan

Most nézzük meg ugyan ezt a lekérdezést egy picit másként:

SELECT *
FROM   
    [dbo].[T1]
WHERE  
    [col3] BETWEEN '20170101' AND '20180101'
AND 
    [col2] BETWEEN 300000000 AND 699999999
ORDER BY [col3] DESC;

Amiben különbözik, az az ORDER BY rész. E miatt most másik futási tervünk van:

Azt is észre kell venni, hogy most egy szálon futott a lekérdezés! Nézzük csak meg az Index Seek operátor tulajdonságait:

Nahát, itt backward scan van, illetve nem parallel terv. 

Lássuk tudok e ezen változtatni.

SELECT *
FROM   
    [dbo].[T1]
WHERE  
    [col3] BETWEEN '20170101' AND '20180101'
AND 
    [col2] BETWEEN 300000000 AND 699999999
ORDER BY [col3] DESC
OPTION (MAXDOP 4) 

Sajnos nem, ami látszik is a futási tervben is:

Esetleg más módon? Pl. egy nem dokumentált query hint segítségével? :)

SELECT *
FROM   
    [dbo].[T1]
WHERE  
    [col3] BETWEEN '20170101' AND '20180101'
AND 
    [col2] BETWEEN 300000000 AND 699999999
ORDER BY [col3] DESC
OPTION(USE HINT('ENABLE_PARALLEL_PLAN_PREFERENCE'))

Aha, itt már jó lesz ez, de a futási tervben megjelent egy Sort operátor is. A költsége is jóval magasabb lett, mint az eddigi összes lekérdezésnek. Az ENABLE_PARALLEL_PLAN_REFERENCE hint SQL Server 2016 Sp1 CU2-től elérhető, de jelenleg nem dokumentált, azaz mindenki csak saját felelősségre használja. Ennek lehet alternatívája egy table hint, a FORCESCAN, ami esetében ugyan ezt a futási tervet kapom, illetve a költsége is ugyan olyan magas lesz.

Konklúzió

Alapvetően mindegy is, hogy backward vagy forward scan van, de akkor már nem, ha parallel futási tervvel (legalábbis némelyik operátor hasznot tud húzni a párhuzamos futásból) gyorsabban ki tudom szolgálni a lekérdezést. Backward scan esetén nem lehetséges mindenhol a parallel futás egyes operátoroknál, így ez egy szálon fog menni, ahogy a példában is látszik. Szóval a lényeg, hogy erre is érdemes figyelni. Aki kicsit mélyebbre megy a kódban, pár furcsa dolgot is észrevehet, pl az index sorrend és az ORDER BY. 

A query és table hint használatát is csak akkor javaslom, amikor a "hagyományos" optimalizálással már nem érünk célt, ez legyen az utolsó, amit bevetünk a lekérdezések "gyorsításához"

Akit mélyebben érdekel a téma, ajánlom a windbg használatát és ott meg lehet nézni a call stack-et is :)

Add comment