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.