Hinweis:Dieser Beitrag wurde ursprünglich nur in unserem eBook „High Performance Techniques for SQL Server, Volume 2“ veröffentlicht. Hier finden Sie Informationen zu unseren eBooks. Beachten Sie auch, dass sich einige dieser Dinge mit den geplanten Erweiterungen von In-Memory OLTP in SQL Server 2016 ändern können.
Es gibt einige Gewohnheiten und Best Practices, die viele von uns im Laufe der Zeit in Bezug auf Transact-SQL-Code entwickeln. Insbesondere bei gespeicherten Prozeduren bemühen wir uns, Parameterwerte des richtigen Datentyps zu übergeben und unsere Parameter explizit zu benennen, anstatt sich nur auf die Ordinalposition zu verlassen. Manchmal werden wir dabei jedoch faul:Wir vergessen vielleicht, einem Unicode-String das Präfix N
voranzustellen , oder listen Sie einfach die Konstanten oder Variablen der Reihe nach auf, anstatt die Parameternamen anzugeben. Oder beides.
Wenn Sie in SQL Server 2014 In-Memory OLTP („Hekaton“) und nativ kompilierte Prozeduren verwenden, sollten Sie Ihre Überlegungen zu diesen Dingen möglicherweise ein wenig anpassen. Ich demonstriere dies anhand von Code anhand des SQL Server 2014 RTM In-Memory-OLTP-Beispiels auf CodePlex, das die AdventureWorks2012-Beispieldatenbank erweitert. (Wenn Sie dies von Grund auf neu einrichten, um mitzumachen, werfen Sie bitte einen kurzen Blick auf meine Beobachtungen in einem früheren Beitrag.)
Werfen wir einen Blick auf die Signatur für die gespeicherte Prozedur 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
Ich war neugierig, ob es eine Rolle spielt, ob die Parameter benannt wurden oder ob nativ kompilierte Prozeduren implizite Konvertierungen als Argumente für gespeicherte Prozeduren besser handhaben als herkömmliche gespeicherte Prozeduren. Zuerst habe ich eine Kopie Sales.usp_InsertSpecialOffer_inmem
erstellt als traditionelle gespeicherte Prozedur – dazu gehörte lediglich das Entfernen des ATOMIC
blockieren und NOT NULL
entfernen Deklarationen aus den Eingabeparametern:
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
Um Verschiebungskriterien zu minimieren, fügt die Prozedur immer noch in die In-Memory-Version der Tabelle, Sales.SpecialOffer_inmem, ein.
Dann wollte ich 100.000 Aufrufe an beide Kopien der gespeicherten Prozedur mit diesen Kriterien terminieren:
Explizit benannte Parameter | Parameter nicht benannt | |
---|---|---|
Alle Parameter vom richtigen Datentyp | x | x |
Einige Parameter haben den falschen Datentyp | x | x |
Unter Verwendung des folgenden Stapels, kopiert für die herkömmliche Version der gespeicherten Prozedur (einfaches Entfernen von _inmem
aus den vier EXEC
Anrufe):
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
Ich habe jeden Test zehnmal ausgeführt, und hier sind die durchschnittlichen Dauern in Millisekunden:
Traditionelle gespeicherte Prozedur | |
---|---|
Parameter | Durchschnittliche Dauer (Millisekunden) |
Benannte, richtige Typen | 72.132 |
Nicht benannt, richtige Typen | 72.846 |
Benannte, unzulässige Typen | 76.154 |
Nicht benannt, falsche Typen | 76.902 |
Nativ kompilierte gespeicherte Prozedur | |
Parameter | Durchschnittliche Dauer (Millisekunden) |
Benannte, richtige Typen | 63.202 |
Nicht benannt, richtige Typen | 61.297 |
Benannte, unzulässige Typen | 64.560 |
Nicht benannt, falsche Typen | 64.288 |
Durchschnittliche Dauer in Millisekunden verschiedener Aufrufmethoden
Bei der herkömmlichen gespeicherten Prozedur ist klar, dass die Verwendung der falschen Datentypen einen erheblichen Einfluss auf die Leistung hat (etwa 4 Sekunden Unterschied), während das Nichtbenennen der Parameter einen viel weniger dramatischen Effekt hatte (etwa 700 ms mehr). Ich habe immer versucht, Best Practices zu befolgen und die richtigen Datentypen zu verwenden sowie alle Parameter zu benennen, und dieser kleine Test scheint zu bestätigen, dass dies von Vorteil sein kann.
Bei der nativ kompilierten Stored Procedure führte die Verwendung falscher Datentypen dennoch zu einem ähnlichen Leistungsabfall wie bei der traditionellen Stored Procedure. Diesmal half die Benennung der Parameter jedoch nicht so sehr; Tatsächlich hatte es einen negativen Einfluss und verlängerte die Gesamtdauer um fast zwei Sekunden. Um fair zu sein, ist dies eine große Anzahl von Anrufen in relativ kurzer Zeit, aber wenn Sie versuchen, die absolut modernste Leistung aus dieser Funktion herauszuholen, zählt jede Nanosekunde.
Entdeckung des Problems
Wie können Sie wissen, ob Ihre nativ kompilierten gespeicherten Prozeduren mit einer dieser "langsamen" Methoden aufgerufen werden? Dafür gibt es ein XEvent! Das Ereignis heißt natively_compiled_proc_slow_parameter_passing
, und es scheint derzeit nicht in Books Online dokumentiert zu sein. Sie können die folgende Sitzung für erweiterte Ereignisse erstellen, um dieses Ereignis zu überwachen:
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;
Sobald die Sitzung läuft, können Sie jeden der vier obigen Aufrufe einzeln versuchen und dann diese Abfrage ausführen:
;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;
Je nachdem, was Sie ausgeführt haben, sollten Sie ähnliche Ergebnisse sehen:
Zeitstempel | db | Objekt-ID | Grund | Charge |
---|---|---|---|---|
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; |
Beispielergebnisse von erweiterten Ereignissen
Hoffentlich der batch
reicht aus, um den Übeltäter zu identifizieren, aber wenn Sie große Batches haben, die mehrere Aufrufe von nativ kompilierten Prozeduren enthalten, und Sie die Objekte aufspüren müssen, die dieses Problem speziell auslösen, können Sie sie einfach mit object_id
in ihren jeweiligen Datenbanken.
Ich empfehle jetzt nicht, alle 400.000 Aufrufe im Text auszuführen, während die Sitzung aktiv ist, oder diese Sitzung in einer Produktionsumgebung mit hoher Parallelität zu aktivieren – wenn Sie dies häufig tun, kann dies zu erheblichem Overhead führen. Es ist viel besser, wenn Sie diese Art von Aktivität in Ihrer Entwicklungs- oder Staging-Umgebung überprüfen, solange Sie sie einer angemessenen Arbeitslast aussetzen können, die einen vollständigen Geschäftszyklus abdeckt.
Schlussfolgerung
Ich war definitiv überrascht von der Tatsache, dass die Benennung von Parametern – lange als Best Practice betrachtet – mit nativ kompilierten Stored Procedures zur Worst Practice geworden ist. Und Microsoft ist bekannt, dass es ein potenzielles Problem genug ist, dass sie ein erweitertes Ereignis erstellt haben, das speziell dafür entwickelt wurde, es zu verfolgen. Wenn Sie In-Memory-OLTP verwenden, sollten Sie dies bei der Entwicklung unterstützender gespeicherter Prozeduren im Auge behalten. Ich weiß, dass ich mein Muskelgedächtnis definitiv davon abbringen muss, benannte Parameter zu verwenden.