sql >> Databasteknik >  >> RDS >> Database

Hur man inte kallar Hekaton inbyggt kompilerade lagrade procedurer

Obs:Det här inlägget publicerades ursprungligen endast i vår e-bok, High Performance Techniques for SQL Server, volym 2. Du kan ta reda på om våra e-böcker här. Observera också att vissa av dessa saker kan förändras med de planerade förbättringarna av In-Memory OLTP i SQL Server 2016.

Det finns några vanor och bästa praxis som många av oss utvecklar med tiden när det gäller Transact-SQL-kod. Speciellt med lagrade procedurer strävar vi efter att skicka parametervärden av rätt datatyp och namnge våra parametrar uttryckligen snarare än att bara förlita oss på ordningsposition. Men ibland kan vi bli lata om detta:vi kanske glömmer att prefixet en Unicode-sträng med N , eller bara lista konstanterna eller variablerna i ordning istället för att ange parameternamnen. Eller båda.

I SQL Server 2014, om du använder In-Memory OLTP ("Hekaton") och inbyggda kompilerade procedurer, kanske du vill justera ditt tänkande om dessa saker lite. Jag ska demonstrera med lite kod mot SQL Server 2014 RTM In-Memory OLTP Sample på CodePlex, som utökar exempeldatabasen AdventureWorks2012. (Om du ska ställa in det här från början för att följa med, vänligen ta en snabb blick på mina observationer i ett tidigare inlägg.)

Låt oss ta en titt på signaturen för den lagrade proceduren Sales.usp_InsertSpecialOffer_inmem :

CREATE PROCEDURE [Sales].[usp_InsertSpecialOffer_inmem] 
	@Description    NVARCHAR(255)  NOT NULL, 
	@DiscountPct    SMALLMONEY     NOT NULL = 0,
	@Type           NVARCHAR(50)   NOT NULL,
	@Category       NVARCHAR(50)   NOT NULL,
	@StartDate      DATETIME2      NOT NULL,
	@EndDate        DATETIME2      NOT NULL,
	@MinQty         INT            NOT NULL = 0,
	@MaxQty         INT                     = NULL,
	@SpecialOfferID INT OUTPUT
WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER
AS
BEGIN ATOMIC 
WITH (TRANSACTION ISOLATION LEVEL=SNAPSHOT, LANGUAGE=N'us_english')
 
	DECLARE @msg nvarchar(256)
 
        -- validation removed for brevity
 
	INSERT Sales.SpecialOffer_inmem (Description, 
		DiscountPct,
		Type,
		Category,
		StartDate,
		EndDate,
		MinQty,
		MaxQty) 
	VALUES (@Description, 
		@DiscountPct,
		@Type,
		@Category,
		@StartDate,
		@EndDate,
		@MinQty,
		@MaxQty)
 
	SET @SpecialOfferID = SCOPE_IDENTITY()
END
GO

Jag var nyfiken på om det spelade någon roll om parametrarna var namngivna eller om inbyggda kompilerade procedurer hanterade implicita omvandlingar som argument till lagrade procedurer bättre än traditionella lagrade procedurer. Först skapade jag en kopia Sales.usp_InsertSpecialOffer_inmem som en traditionell lagrad procedur – detta innebar bara att ta bort ATOMIC blockera och ta bort NOT NULL deklarationer från indataparametrarna:

CREATE PROCEDURE [Sales].[usp_InsertSpecialOffer] 
	@Description    NVARCHAR(255), 
	@DiscountPct    SMALLMONEY     = 0,
	@Type           NVARCHAR(50),
	@Category       NVARCHAR(50),
	@StartDate      DATETIME2,
	@EndDate        DATETIME2,
	@MinQty         INT            = 0,
	@MaxQty         INT            = NULL,
	@SpecialOfferID INT OUTPUT
AS
BEGIN
	DECLARE @msg nvarchar(256)
 
        -- validation removed for brevity
 
	INSERT Sales.SpecialOffer_inmem (Description, 
		DiscountPct,
		Type,
		Category,
		StartDate,
		EndDate,
		MinQty,
		MaxQty) 
	VALUES (@Description, 
		@DiscountPct,
		@Type,
		@Category,
		@StartDate,
		@EndDate,
		@MinQty,
		@MaxQty)
 
	SET @SpecialOfferID = SCOPE_IDENTITY()
END
GO

För att minimera förskjutningskriterier, infogas proceduren fortfarande i In-Memory-versionen av tabellen, Sales.SpecialOffer_inmem.

Sedan ville jag tajma 100 000 samtal till båda kopiorna av den lagrade proceduren med dessa kriterier:

Parametrar som är explicit namngivna Parametrar inte namngivna
Alla parametrar av korrekt datatyp x x
Vissa parametrar av fel datatyp x x


Med följande batch, kopierad för den traditionella versionen av den lagrade proceduren (bara att ta bort _inmem från de fyra EXEC samtal):

SET NOCOUNT ON;
 
CREATE TABLE #x
(
  i INT IDENTITY(1,1),
  d VARCHAR(32), 
  s DATETIME2(7) NOT NULL DEFAULT SYSDATETIME(), 
  e DATETIME2(7)
);
GO
 
INSERT #x(d) VALUES('Named, proper types');
GO
 
/* this uses named parameters, and uses correct data types */
 
DECLARE 
	@p1 NVARCHAR(255) = N'Product 1',
	@p2 SMALLMONEY    = 10,
	@p3 NVARCHAR(50)  = N'Volume Discount',
	@p4 NVARCHAR(50)  = N'Reseller',
	@p5 DATETIME2     = '20140615',
	@p6 DATETIME2     = '20140620',
	@p7 INT           = 10, 
	@p8 INT           = 20, 
	@p9 INT;
 
EXEC Sales.usp_InsertSpecialOffer_inmem 
	@Description    = @p1,
	@DiscountPct    = @p2,
	@Type           = @p3,
	@Category       = @p4,
	@StartDate      = @p5,
	@EndDate        = @p6,
	@MinQty         = @p7,
	@MaxQty         = @p8,
	@SpecialOfferID = @p9 OUTPUT;
 
GO 100000
 
UPDATE #x SET e = SYSDATETIME() WHERE i = 1;
GO
 
DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1';
GO
 
INSERT #x(d) VALUES('Not named, proper types');
GO
 
/* this does not use named parameters, but uses correct data types */
 
DECLARE 
	@p1 NVARCHAR(255) = N'Product 1',
	@p2 SMALLMONEY    = 10,
	@p3 NVARCHAR(50)  = N'Volume Discount',
	@p4 NVARCHAR(50)  = N'Reseller',
	@p5 DATETIME2     = '20140615',
	@p6 DATETIME2     = '20140620',
	@p7 INT           = 10, 
	@p8 INT           = 20, 
	@p9 INT;
 
EXEC Sales.usp_InsertSpecialOffer_inmem 
	@p1, @p2, @p3, @p4, @p5, 
	@p6, @p7, @p8, @p9 OUTPUT;
 
GO 100000
 
UPDATE #x SET e = SYSDATETIME() WHERE i = 2;
GO
 
DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1';
GO
 
INSERT #x(d) VALUES('Named, improper types');
GO
 
/* this uses named parameters, but incorrect data types */
 
DECLARE 
	@p1 VARCHAR(255)  = 'Product 1',
	@p2 DECIMAL(10,2) = 10,
	@p3 VARCHAR(255)  = 'Volume Discount',
	@p4 VARCHAR(32)   = 'Reseller',
	@p5 DATETIME      = '20140615',
	@p6 CHAR(8)       = '20140620',
	@p7 TINYINT       = 10, 
	@p8 DECIMAL(10,2) = 20, 
	@p9 BIGINT;
 
EXEC Sales.usp_InsertSpecialOffer_inmem 
	@Description    = @p1,
	@DiscountPct    = @p2,
	@Type           = @p3,
	@Category       = @p4,
	@StartDate      = @p5,
	@EndDate        = @p6,
	@MinQty         = '10',
	@MaxQty         = @p8,
	@SpecialOfferID = @p9 OUTPUT;
 
GO 100000
 
UPDATE #x SET e = SYSDATETIME() WHERE i = 3;
GO
 
DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1';
GO
 
INSERT #x(d) VALUES('Not named, improper types');
GO
 
/* this does not use named parameters, and uses incorrect data types */
 
DECLARE 
	@p1 VARCHAR(255)  = 'Product 1',
	@p2 DECIMAL(10,2) = 10,
	@p3 VARCHAR(255)  = 'Volume Discount',
	@p4 VARCHAR(32)   = 'Reseller',
	@p5 DATETIME      = '20140615',
	@p6 CHAR(8)       = '20140620',
	@p7 TINYINT       = 10, 
	@p8 DECIMAL(10,2) = 20, 
	@p9 BIGINT;
 
EXEC Sales.usp_InsertSpecialOffer_inmem 
	@p1, @p2, @p3, @p4, @p5, 
	@p6, '10', @p8, @p9 OUTPUT;
 
GO 100000
 
UPDATE #x SET e = SYSDATETIME() WHERE i = 4;
GO
DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1';
GO
 
SELECT d, duration_ms = DATEDIFF(MILLISECOND, s, e) FROM #x;
GO
DROP TABLE #x;
GO

Jag körde varje test 10 gånger, och här var den genomsnittliga varaktigheten, i millisekunder:

Traditionell lagrad procedur
Parametrar Genomsnittlig varaktighet
(millisekunder)
Namngivna, korrekta typer 72 132
Inte namngiven, korrekta typer 72 846
Namngivna, olämpliga typer 76 154
Inte namngiven, felaktiga typer 76 902
Natively kompilerad lagrad procedur
Parametrar Genomsnittlig varaktighet
(millisekunder)
Namngivna, korrekta typer 63 202
Inte namngiven, korrekta typer 61 297
Namngivna, olämpliga typer 64 560
Inte namngiven, felaktiga typer 64 288

Genomsnittlig varaktighet, i millisekunder, för olika anropsmetoder

Med den traditionella lagrade proceduren är det tydligt att användning av fel datatyper har en betydande inverkan på prestandan (cirka 4 sekunders skillnad), samtidigt som att inte namnge parametrarna hade en mycket mindre dramatisk effekt (tillför cirka 700 ms). Jag har alltid försökt följa bästa praxis och använda rätt datatyper samt namnge alla parametrar, och det här lilla testet verkar bekräfta att det kan vara fördelaktigt att göra det.

Med den inbyggt kompilerade lagrade proceduren ledde användningen av fel datatyper fortfarande till en liknande prestandaminskning som med den traditionella lagrade proceduren. Men den här gången hjälpte det inte så mycket att namnge parametrarna; i själva verket hade det en negativ inverkan, och lade till nästan två sekunder till den totala varaktigheten. För att vara rättvis är det här ett stort antal samtal på ganska kort tid, men om du försöker få ut den absolut mest avancerade prestanda du kan ur den här funktionen, räknas varje nanosekund.

Att upptäcka problemet

Hur kan du veta om dina inbyggt kompilerade lagrade procedurer anropas med någon av dessa "långsamma" metoder? Det finns ett XEvent för det! Händelsen kallas natively_compiled_proc_slow_parameter_passing , och det verkar inte vara dokumenterat i Books Online just nu. Du kan skapa följande utökade händelsesession för att övervaka denna händelse:

CREATE EVENT SESSION [XTP_Parameter_Events] ON SERVER 
ADD EVENT sqlserver.natively_compiled_proc_slow_parameter_passing
(
    ACTION(sqlserver.sql_text)
) 
ADD TARGET package0.event_file(SET filename=N'C:\temp\XTPParams.xel');
GO
ALTER EVENT SESSION [XTP_Parameter_Events] ON SERVER STATE = START;

När sessionen körs kan du prova något av ovanstående fyra anrop individuellt, och sedan kan du köra den här frågan:

;WITH x([timestamp], db, [object_id], reason, batch)
AS
(
  SELECT 
    xe.d.value(N'(event/@timestamp)[1]',N'datetime2(0)'),
    DB_NAME(xe.d.value(N'(event/data[@name="database_id"]/value)[1]',N'int')),
    xe.d.value(N'(event/data[@name="object_id"]/value)[1]',N'int'),
    xe.d.value(N'(event/data[@name="reason"]/text)[1]',N'sysname'),
    xe.d.value(N'(event/action[@name="sql_text"]/value)[1]',N'nvarchar(max)')
  FROM 
    sys.fn_xe_file_target_read_file(N'C:\temp\XTPParams*.xel',NULL,NULL,NULL) AS ft
    CROSS APPLY (SELECT CONVERT(XML, ft.event_data)) AS xe(d)
)
SELECT [timestamp], db, [object_id], reason, batch FROM x;

Beroende på vad du körde bör du se resultat som liknar detta:

tidsstämpel db objekt-id orsak batch
2014-07-01 16:23:14 AdventureWorks2012 2087678485 named_parameters
DECLARE 
	@p1 NVARCHAR(255) = N'Product 1',
	@p2 SMALLMONEY    = 10,
	@p3 NVARCHAR(50)  = N'Volume Discount',
	@p4 NVARCHAR(50)  = N'Reseller',
	@p5 DATETIME2     = '20140615',
	@p6 DATETIME2     = '20140620',
	@p7 INT           = 10, 
	@p8 INT           = 20, 
	@p9 INT;

EXEC Sales.usp_InsertSpecialOffer_inmem 
	@Description    = @p1,
	@DiscountPct    = @p2,
	@Type           = @p3,
	@Category       = @p4,
	@StartDate      = @p5,
	@EndDate        = @p6,
	@MinQty         = @p7,
	@MaxQty         = @p8,
	@SpecialOfferID = @p9 OUTPUT;
2014-07-01 16:23:22 AdventureWorks2012 2087678485 parameter_conversion
DECLARE 
	@p1 VARCHAR(255)  = 'Product 1',
	@p2 DECIMAL(10,2) = 10,
	@p3 VARCHAR(255)  = 'Volume Discount',
	@p4 VARCHAR(32)   = 'Reseller',
	@p5 DATETIME      = '20140615',
	@p6 CHAR(8)       = '20140620',
	@p7 TINYINT       = 10, 
	@p8 DECIMAL(10,2) = 20, 
	@p9 BIGINT;

EXEC Sales.usp_InsertSpecialOffer_inmem 
	@p1, @p2, @p3, @p4, @p5, 
	@p6, '10', @p8, @p9 OUTPUT;

Exempelresultat från utökade evenemang

Förhoppningsvis batchen kolumnen räcker för att identifiera den skyldige, men om du har stora partier som innehåller flera anrop till inbyggt kompilerade procedurer och du behöver spåra de objekt som specifikt utlöser detta problem, kan du helt enkelt slå upp dem med objekt_id i sina respektive databaser.

Nu rekommenderar jag inte att köra alla 400 000 anrop i texten medan sessionen är aktiv, eller att aktivera den här sessionen i en mycket samtidig produktionsmiljö – om du gör det här mycket kan det orsaka betydande overhead. Du är mycket bättre av att kolla efter den här typen av aktivitet i din utvecklings- eller iscensättningsmiljö, så länge du kan utsätta den för en ordentlig arbetsbelastning som täcker en hel affärscykel.

Slutsats

Jag blev definitivt förvånad över det faktum att namngivningsparametrar – länge ansett som en bästa praxis – har förvandlats till en sämsta praxis med inbyggda lagrade procedurer. Och det är känt av Microsoft för att vara tillräckligt av ett potentiellt problem att de skapade en utökad händelse designad speciellt för att spåra det. Om du använder In-Memory OLTP, är detta en sak du bör ha på din radar när du utvecklar stödjande lagrade procedurer. Jag vet att jag definitivt kommer att behöva avträna mitt muskelminne från att använda namngivna parametrar.


  1. Importera MySQL-databas till en MS SQL Server

  2. SQL Server-låsets anatomi och de bästa sätten att undvika dem

  3. Vilka är fördelarna och nackdelarna med att behålla SQL i lagrade processer kontra kod

  4. Rekursiv fråga används för transitiv stängning