sql >> Databasteknik >  >> RDS >> Sqlserver

Hjälper det att tilldela indataparametrar för lagrad procedur till lokala variabler att optimera frågan?

Jag kommer inte att försöka förklara alla detaljer om parametersniffning, men kort sagt, nej det gör det inte alltid hjälp (och det kan hindra).

Föreställ dig en tabell (T) med en primärnyckel och en indexerad datumkolumn (A), i tabellen finns det 1 000 rader, 400 har samma värde på A (låt säga idag 20130122), de återstående 600 raderna är de kommande 600 dagarna , alltså endast 1 post per datum.

Denna fråga:

SELECT *
FROM T
WHERE A = '20130122';

Kommer att ge en annan utförandeplan för att:

SELECT *
FROM T
WHERE A = '20130123';

Eftersom statistiken kommer att indikera att för de första 400 av 1 000 raderna kommer att returneras, bör optimeraren inse att en tabellsökning kommer att vara effektivare än en bokmärkessökning, medan den andra bara kommer att ge 1 rader, så en bokmärkessökning kommer att vara mycket effektivare.

Nu, tillbaka till din fråga, om vi gjorde detta till en procedur:

CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param

Kör sedan

EXECUTE dbo.GetFromT '20130122'; --400 rows

Frågeplanen med tabellskanningen kommer att användas, om första gången du kör den använder du '20130123' som parameter kommer den att lagra bokmärkesuppslagsplanen. Tills förfarandet är omkompilerat kommer planen att förbli densamma. Att göra något så här:

CREATE PROCEDURE dbo.GetFromT @Param VARCHAR(5)
AS
    DECLARE @Param2 VARCHAR(5) = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2

Sedan körs detta:

EXECUTE dbo.GetFromT '20130122';

Även om proceduren kompileras på en gång, flyter den inte ordentligt, så frågeplanen som skapades vid den första kompileringen har ingen aning om att @Param2 kommer att bli samma som @param, så optimeraren (utan kunskap om hur många rader som ska förvänta) kommer att anta att 300 kommer att returneras (30%), eftersom en tabellsökning kommer att anses vara effektivare än en bokmärkessökning. Om du körde samma procedur med '20130123' som parameter skulle det ge samma plan (oavsett vilken parameter den först anropades med) eftersom statistiken inte kan användas för ett okänt värde. Så att köra den här proceduren för '20130122' skulle vara effektivare, men för alla andra värden skulle det vara mindre effektivt än utan lokala parametrar (förutsatt att proceduren utan lokala parametrar först anropades med allt annat än '20130122')

Några frågor att demonstrera så att du själv kan se genomförandeplaner

Skapa schema och exempeldata

CREATE TABLE T (ID INT IDENTITY(1, 1) PRIMARY KEY, A DATE NOT NULL, B INT,C INT, D INT, E INT);

CREATE NONCLUSTERED INDEX IX_T ON T (A);

INSERT T (A, B, C, D, E)
SELECT  TOP 400 CAST('20130122' AS DATE), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   type = 'P'
UNION ALL
SELECT TOP 600 DATEADD(DAY, number, CAST('20130122' AS DATE)), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   Type = 'P';
GO
CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param
GO
CREATE PROCEDURE dbo.GetFromT2 @Param DATE
AS
    DECLARE @Param2 DATE = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2
GO

Kör procedurer (visar faktisk utförandeplan):

EXECUTE GetFromT '20130122';
EXECUTE GetFromT '20130123';
EXECUTE GetFromT2 '20130122';
EXECUTE GetFromT2 '20130123';
GO
EXECUTE SP_RECOMPILE GetFromT;
EXECUTE SP_RECOMPILE GetFromT2;
GO
EXECUTE GetFromT '20130123';
EXECUTE GetFromT '20130122';
EXECUTE GetFromT2 '20130123';
EXECUTE GetFromT2 '20130122';

Du kommer att se det första gången GetFromT är kompilerad använder den en tabellsökning och behåller denna när den körs med parametern '20130122', GetFromT2 använder också en tabellskanning och behåller planen för '20130122'.

Efter att procedurerna har ställts in för omkompilering och körs igen (notera i en annan ordning), GetFromT använder en bokmärkesloopup och behåller planen för '20130122', trots att man tidigare ansett att en tabellskanning är en mer lämplig plan. GetFromT2 är opåverkad av ordern och har samma plan som före återuppfyllelsen.

Så sammanfattningsvis beror det på fördelningen av dina data och dina index, din frekvens av omkompilering och lite tur om huruvida en procedur kommer att dra nytta av att använda lokala variabler. Det gör det verkligen inte alltid hjälp.

Förhoppningsvis har jag belyst effekten av att använda lokala parametrar, exekveringsplaner och kompilering av lagrade procedurer. Om jag har misslyckats helt eller missat en nyckelpunkt kan en mycket mer djupgående förklaring hittas här:

http://www.sommarskog.se/query-plan-mysteries.html



  1. Varför skriver PDO ut mitt lösenord när anslutningen misslyckas?

  2. 3 metoder för att ansluta MDF-fil till SQL Server

  3. bästa sättet att anropa två databasservrar

  4. Anslut java till mysql med jdbc på osx