sql >> Database teknologi >  >> RDS >> Database

Hvordan man ikke kalder Hekaton native-kompilerede lagrede procedurer

Bemærk:Dette indlæg blev oprindeligt kun offentliggjort i vores e-bog, High Performance Techniques for SQL Server, bind 2. Du kan finde ud af om vores e-bøger her. Bemærk også, at nogle af disse ting kan ændre sig med de planlagte forbedringer af In-Memory OLTP i SQL Server 2016.

Der er nogle vaner og bedste praksis, som mange af os udvikler over tid med hensyn til Transact-SQL-kode. Med især lagrede procedurer stræber vi efter at videregive parameterværdier af den korrekte datatype og navngive vores parametre eksplicit i stedet for udelukkende at stole på ordensposition. Nogle gange kan vi dog blive dovne over dette:vi glemmer måske at præfikse en Unicode-streng med N , eller bare angiv konstanterne eller variablerne i rækkefølge i stedet for at angive parameternavnene. Eller begge dele.

I SQL Server 2014, hvis du bruger In-Memory OLTP ("Hekaton") og indbyggede kompilerede procedurer, vil du måske justere din tankegang om disse ting en smule. Jeg vil demonstrere med noget kode mod SQL Server 2014 RTM In-Memory OLTP Sample på CodePlex, som udvider AdventureWorks2012-eksempeldatabasen. (Hvis du vil sætte dette op fra bunden for at følge med, bedes du tage et hurtigt blik på mine observationer i et tidligere indlæg.)

Lad os tage et kig på signaturen for den lagrede procedure 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

Jeg var nysgerrig, om det havde nogen betydning, om parametrene blev navngivet, eller om native kompilerede procedurer håndterede implicitte konverteringer som argumenter til lagrede procedurer bedre end traditionelle lagrede procedurer. Først oprettede jeg en kopi Sales.usp_InsertSpecialOffer_inmem som en traditionel lagret procedure – dette involverede blot at fjerne ATOMIC blokere og fjerne NOT NULL erklæringer fra inputparametrene:

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

For at minimere forskydningskriterier indsættes proceduren stadig i In-Memory-versionen af ​​tabellen, Sales.SpecialOffer_inmem.

Så ønskede jeg at time 100.000 opkald til begge kopier af den lagrede procedure med disse kriterier:

Parametre navngivet eksplicit Parametre ikke navngivet
Alle parametre af korrekt datatype x x
Nogle parametre af forkert datatype x x


Ved brug af følgende batch, kopieret til den traditionelle version af den lagrede procedure (simpelthen fjernelse af _inmem fra de fire EXEC opkald):

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

Jeg kørte hver test 10 gange, og her var de gennemsnitlige varigheder i millisekunder:

Traditionel lagret procedure
Parametre Gennemsnitlig varighed
(millisekunder)
Navngivne, rigtige typer 72.132
Ikke navngivet, korrekte typer 72.846
Navngivne, ukorrekte typer 76.154
Ikke navngivet, ukorrekte typer 76.902
Native kompileret lagret procedure
Parametre Gennemsnitlig varighed
(millisekunder)
Navngivne, rigtige typer 63.202
Ikke navngivet, korrekte typer 61.297
Navngivne, ukorrekte typer 64.560
Ikke navngivet, ukorrekte typer 64.288

Gennemsnitlig varighed, i millisekunder, af forskellige opkaldsmetoder

Med den traditionelle lagrede procedure er det klart, at brug af de forkerte datatyper har en væsentlig indvirkning på ydeevnen (ca. 4 sekunders forskel), mens ikke at navngive parametrene havde en meget mindre dramatisk effekt (tilføj ca. 700ms). Jeg har altid forsøgt at følge bedste praksis og bruge de rigtige datatyper samt navngive alle parametre, og denne lille test ser ud til at bekræfte, at det kan være en fordel at gøre det.

Med den oprindeligt kompilerede lagrede procedure førte brug af de forkerte datatyper stadig til et lignende fald i ydeevne som med den traditionelle lagrede procedure. Denne gang hjalp det dog ikke så meget at navngive parametrene; faktisk havde det en negativ indvirkning og tilføjede næsten to sekunder til den samlede varighed. For at være retfærdig er dette et stort antal opkald på ret kort tid, men hvis du forsøger at presse den absolut mest avancerede ydeevne ud af denne funktion, tæller hvert nanosekund.

Opdagelse af problemet

Hvordan kan du vide, om dine oprindeligt kompilerede lagrede procedurer bliver kaldt med en af ​​disse "langsomme" metoder? Der er en XEvent for det! Hændelsen kaldes natively_compiled_proc_slow_parameter_passing , og det ser ikke ud til at være dokumenteret i Books Online på nuværende tidspunkt. Du kan oprette følgende udvidede begivenhedssession for at overvåge denne begivenhed:

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ører, kan du prøve et af ovenstående fire opkald individuelt, og derefter kan du køre denne forespørgsel:

;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;

Afhængigt af hvad du kørte, bør du se resultater svarende til dette:

tidsstempel db objekt-id årsag batch
2014-07-01 16:23:14 AdventureWorks2012 2087678485 navngivne_parametre
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_konvertering
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;

Eksempler på resultater fra udvidede begivenheder

Forhåbentlig er batchen kolonnen er nok til at identificere den skyldige, men hvis du har store batches, der indeholder flere kald til native kompilerede procedurer, og du skal spore de objekter, der specifikt udløser dette problem, kan du blot slå dem op ved at object_id i deres respektive databaser.

Nu anbefaler jeg ikke at køre alle 400.000 opkald i teksten, mens sessionen er aktiv, eller at aktivere denne session i et meget samtidig produktionsmiljø – hvis du gør dette meget, kan det forårsage betydelige overhead. Du er meget bedre stillet at tjekke for denne form for aktivitet i dit udviklings- eller iscenesættelsesmiljø, så længe du kan udsætte det for en ordentlig arbejdsbyrde, der dækker en fuld forretningscyklus.

Konklusion

Jeg var bestemt overrasket over, at navngivningsparametre – længe betragtet som en bedste praksis – er blevet omdannet til en værste praksis med indbyggede lagrede procedurer. Og det er kendt af Microsoft for at være nok af et potentielt problem, at de oprettede en udvidet begivenhed designet specifikt til at spore den. Hvis du bruger In-Memory OLTP, er dette én ting, du bør holde på din radar, mens du udvikler understøttende lagrede procedurer. Jeg ved, at jeg helt sikkert bliver nødt til at aftræne min muskelhukommelse fra at bruge navngivne parametre.


  1. POWER() Eksempler i SQL Server

  2. Hvad er bedre for din big data-applikation, SQL eller NoSQL?

  3. Forståelse af Java Support for Persistence med JPA

  4. MariaDB LCASE() Forklaret