Database
 sql >> Datenbank >  >> RDS >> Database

Wie man nativ kompilierte gespeicherte Prozeduren von Hekaton nicht aufruft

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.