sql >> Database >  >> RDS >> Database

Hoe u de native gecompileerde opgeslagen procedures van Hekaton niet aanroept?

Opmerking:dit bericht is oorspronkelijk alleen gepubliceerd in ons eBook, High Performance Techniques for SQL Server, Volume 2. U kunt hier meer te weten komen over onze eBooks. Houd er ook rekening mee dat sommige van deze dingen kunnen veranderen met de geplande verbeteringen aan In-Memory OLTP in SQL Server 2016.

Er zijn enkele gewoonten en best practices die velen van ons in de loop van de tijd ontwikkelen met betrekking tot Transact-SQL-code. Met name bij opgeslagen procedures streven we ernaar parameterwaarden van het juiste gegevenstype door te geven en onze parameters expliciet te noemen in plaats van alleen te vertrouwen op de ordinale positie. Soms worden we hier echter lui van:we vergeten misschien een Unicode-tekenreeks te laten voorafgaan door N , of vermeld de constanten of variabelen in volgorde in plaats van de parameternamen op te geven. Of beide.

Als u in SQL Server 2014 In-Memory OLTP ("Hekaton") en native gecompileerde procedures gebruikt, wilt u misschien uw mening over deze dingen een beetje aanpassen. Ik zal demonstreren met wat code tegen het SQL Server 2014 RTM In-Memory OLTP-voorbeeld op CodePlex, dat de voorbeelddatabase van AdventureWorks2012 uitbreidt. (Als je dit van de grond af aan gaat opzetten om het te volgen, kijk dan even naar mijn observaties in een vorig bericht.)

Laten we eens kijken naar de handtekening voor de opgeslagen 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

Ik was benieuwd of het ertoe deed of de parameters een naam kregen, of dat native gecompileerde procedures impliciete conversies als argumenten voor opgeslagen procedures beter afhandelden dan traditionele opgeslagen procedures. Eerst heb ik een kopie gemaakt Sales.usp_InsertSpecialOffer_inmem als een traditionele opgeslagen procedure – dit omvatte alleen het verwijderen van de ATOMIC blokkeren en het verwijderen van de NOT NULL verklaringen van de invoerparameters:

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

Om de verschuivingscriteria te minimaliseren, wordt de procedure nog steeds ingevoegd in de In-Memory-versie van de tabel, Sales.SpecialOffer_inmem.

Vervolgens wilde ik 100.000 oproepen naar beide exemplaren van de opgeslagen procedure timen met deze criteria:

Parameters expliciet genoemd Parameters niet genoemd
Alle parameters van het juiste gegevenstype x x
Sommige parameters van het verkeerde datatype x x


Met behulp van de volgende batch, gekopieerd voor de traditionele versie van de opgeslagen procedure (eenvoudigweg het verwijderen van _inmem van de vier EXEC oproepen):

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

Ik heb elke test 10 keer uitgevoerd, en dit waren de gemiddelde duur, in milliseconden:

Traditionele opgeslagen procedure
Parameters Gemiddelde duur
(milliseconden)
Benoemde, juiste typen 72.132
Niet genoemd, juiste typen 72.846
Benoemde, onjuiste typen 76.154
Niet genoemd, onjuiste typen 76.902
Natively gecompileerde opgeslagen procedure
Parameters Gemiddelde duur
(milliseconden)
Benoemde, juiste typen 63.202
Niet genoemd, juiste typen 61.297
Benoemde, onjuiste typen 64.560
Niet genoemd, onjuiste typen 64.288

Gemiddelde duur, in milliseconden, van verschillende oproepmethoden

Met de traditionele opgeslagen procedure is het duidelijk dat het gebruik van de verkeerde datatypes een substantiële impact heeft op de prestaties (ongeveer een verschil van 4 seconden), terwijl het niet noemen van de parameters een veel minder dramatisch effect had (ongeveer 700 ms toevoegen). Ik heb altijd geprobeerd de best practices te volgen en de juiste gegevenstypen te gebruiken en alle parameters te noemen, en deze kleine test lijkt te bevestigen dat dit nuttig kan zijn.

Met de native gecompileerde opgeslagen procedure leidde het gebruik van de verkeerde gegevenstypen nog steeds tot een vergelijkbare daling van de prestaties als bij de traditionele opgeslagen procedure. Deze keer hielp het benoemen van de parameters echter niet zo veel; in feite had het een negatief effect en voegde bijna twee seconden toe aan de totale duur. Om eerlijk te zijn, dit is een groot aantal oproepen in een vrij korte tijd, maar als je de absoluut meest geavanceerde prestaties probeert te persen die je uit deze functie kunt halen, telt elke nanoseconde.

Het probleem ontdekken

Hoe weet u of uw native gecompileerde opgeslagen procedures worden aangeroepen met een van deze "trage" methoden? Daar is een XEvent voor! De gebeurtenis heet natively_compiled_proc_slow_parameter_passing , en het lijkt op dit moment niet te zijn gedocumenteerd in Books Online. U kunt de volgende Extended Events-sessie maken om voor deze gebeurtenis te controleren:

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;

Zodra de sessie actief is, kunt u elk van de vier bovenstaande oproepen afzonderlijk proberen, en dan kunt u deze query uitvoeren:

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

Afhankelijk van wat je hebt uitgevoerd, zou je vergelijkbare resultaten moeten zien:

tijdstempel db object_id reden 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;

Voorbeeldresultaten van uitgebreide evenementen

Hopelijk is de batch kolom is voldoende om de boosdoener te identificeren, maar als u grote batches heeft die meerdere aanroepen naar native gecompileerde procedures bevatten en u de objecten moet opsporen die dit probleem specifiek veroorzaken, kunt u ze eenvoudig opzoeken met object_id in hun respectievelijke databases.

Nu raad ik niet aan om alle 400.000 oproepen in de tekst uit te voeren terwijl de sessie actief is, of deze sessie aan te zetten in een zeer gelijktijdige productieomgeving - als u dit veel doet, kan dit aanzienlijke overhead veroorzaken. U kunt dit soort activiteiten veel beter controleren in uw ontwikkel- of staging-omgeving, zolang u het maar kunt onderwerpen aan een behoorlijke werklast die een volledige bedrijfscyclus dekt.

Conclusie

Ik was absoluut verrast door het feit dat het benoemen van parameters - lang beschouwd als een beste praktijk - is veranderd in een slechtste praktijk met native gecompileerde opgeslagen procedures. En het is bij Microsoft bekend dat het een potentieel probleem is dat ze een Extended Event hebben gemaakt dat speciaal is ontworpen om het te volgen. Als u In-Memory OLTP gebruikt, moet u dit in de gaten houden terwijl u ondersteunende opgeslagen procedures ontwikkelt. Ik weet zeker dat ik mijn spiergeheugen moet afleren om benoemde parameters te gebruiken.


  1. GETDATE() Voorbeelden in SQL Server (T-SQL)

  2. Installeren en werken met MySQL 5 op Windows 7

  3. PHP &MySQL paginering

  4. Meerdere databaseverbindingen in Rails