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

Come non chiamare le stored procedure Hekaton compilate in modo nativo

Nota:questo post è stato originariamente pubblicato solo nel nostro eBook, High Performance Techniques for SQL Server, Volume 2. Puoi trovare informazioni sui nostri eBook qui. Tieni inoltre presente che alcune di queste cose potrebbero cambiare con i miglioramenti pianificati a OLTP in memoria in SQL Server 2016.

Ci sono alcune abitudini e best practice che molti di noi sviluppano nel tempo per quanto riguarda il codice Transact-SQL. Con le stored procedure in particolare, ci sforziamo di passare i valori dei parametri del tipo di dati corretto e denominare i nostri parametri in modo esplicito anziché basarsi esclusivamente sulla posizione ordinale. A volte, però, potremmo diventare pigri per questo:potremmo dimenticare di anteporre a una stringa Unicode N o semplicemente elencare le costanti o le variabili in ordine invece di specificare i nomi dei parametri. O entrambi.

In SQL Server 2014, se utilizzi OLTP in memoria ("Hekaton") e procedure compilate in modo nativo, potresti voler modificare un po' il tuo pensiero su queste cose. Dimostrerò con del codice rispetto all'esempio OLTP in memoria RTM di SQL Server 2014 su CodePlex, che estende il database di esempio AdventureWorks2012. (Se hai intenzione di configurarlo da zero per continuare, dai una rapida occhiata alle mie osservazioni in un post precedente.)

Diamo un'occhiata alla firma per la procedura memorizzata 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

Ero curioso di sapere se importava se i parametri fossero denominati o se le procedure compilate in modo nativo gestissero le conversioni implicite come argomenti per le procedure memorizzate meglio delle procedure memorizzate tradizionali. Per prima cosa ho creato una copia Sales.usp_InsertSpecialOffer_inmem come una procedura memorizzata tradizionale:ciò comportava semplicemente la rimozione di ATOMIC bloccare e rimuovere il NOT NULL dichiarazioni dai parametri di input:

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

Per ridurre al minimo i criteri di spostamento, la procedura inserisce comunque nella versione In-Memory della tabella, Sales.SpecialOffer_inmem.

Quindi ho voluto cronometrare 100.000 chiamate a entrambe le copie della procedura memorizzata con questi criteri:

Parametri denominati in modo esplicito Parametri senza nome
Tutti i parametri del tipo di dati corretto x x
Alcuni parametri di tipo di dati errato x x


Utilizzando il seguente batch, copiato per la versione tradizionale della stored procedure (semplicemente rimuovendo _inmem dai quattro EXEC chiamate):

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

Ho eseguito ogni test 10 volte, ed ecco le durate medie, in millisecondi:

Procedura archiviata tradizionale
Parametri Durata media
(millisecondi)
Tipi propri e con nome 72.132
Tipi propri non denominati 72.846
Tipi impropri con nome 76.154
Tipi non denominati, impropri 76.902
Procedura memorizzata compilata in modo nativo
Parametri Durata media
(millisecondi)
Tipi propri e con nome 63.202
Tipi propri non denominati 61.297
Tipi impropri con nome 64.560
Tipi non denominati, impropri 64.288

Durata media, in millisecondi, di vari metodi di chiamata

Con la tradizionale procedura memorizzata, è chiaro che l'utilizzo di tipi di dati errati ha un impatto sostanziale sulle prestazioni (circa 4 secondi di differenza), mentre non nominare i parametri ha avuto un effetto molto meno drammatico (aggiungendo circa 700 ms). Ho sempre cercato di seguire le migliori pratiche e di utilizzare i tipi di dati corretti, nonché di nominare tutti i parametri e questo piccolo test sembra confermare che ciò può essere vantaggioso.

Con la stored procedure compilata in modo nativo, l'utilizzo di tipi di dati errati portava comunque a un calo delle prestazioni simile a quello della stored procedure tradizionale. Questa volta, però, nominare i parametri non ha aiutato molto; infatti ha avuto un impatto negativo, aggiungendo quasi due secondi alla durata complessiva. Ad essere onesti, si tratta di un gran numero di chiamate in un tempo abbastanza breve, ma se stai cercando di ottenere le prestazioni più all'avanguardia in assoluto che puoi ottenere da questa funzione, ogni nanosecondo conta.

Scoprire il problema

Come puoi sapere se le tue stored procedure compilate in modo nativo vengono chiamate con uno di questi metodi "lenti"? C'è un XEvent per questo! L'evento si chiama natively_compiled_proc_slow_parameter_passing , e al momento non sembra essere documentato nella documentazione in linea. Puoi creare la seguente sessione di eventi estesi da monitorare per questo evento:

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;

Una volta che la sessione è in esecuzione, puoi provare una qualsiasi delle quattro chiamate precedenti individualmente, quindi puoi eseguire questa query:

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

A seconda di cosa hai eseguito, dovresti vedere risultati simili a questo:

timestamp db id_oggetto motivo lotto
01-07-2014 16:23:14 AdventureWorks2012 2087678485 parametri_nominati
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;
01-07-2014 16:23:22 AdventureWorks2012 2087678485 conversione_parametro
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;

Risultati di esempio da eventi estesi

Si spera che il batch colonna è sufficiente per identificare il colpevole, ma se hai batch di grandi dimensioni che contengono più chiamate a procedure compilate in modo nativo e devi rintracciare gli oggetti che stanno attivando specificamente questo problema, puoi semplicemente cercarli da object_id nei rispettivi database.

Ora, non consiglio di eseguire tutte le 400.000 chiamate nel testo mentre la sessione è attiva o di attivare questa sessione in un ambiente di produzione altamente simultaneo:se lo fai spesso, può causare un sovraccarico significativo. È molto meglio controllare questo tipo di attività nel tuo ambiente di sviluppo o di staging, purché tu possa sottoporlo a un carico di lavoro adeguato che copra un intero ciclo economico.

Conclusione

Sono stato decisamente sorpreso dal fatto che la denominazione dei parametri, a lungo considerata una best practice, sia stata trasformata in una pratica peggiore con le stored procedure compilate in modo nativo. Ed è noto a Microsoft per essere un potenziale problema sufficiente a creare un evento esteso progettato specificamente per tenerne traccia. Se si utilizza OLTP in memoria, questa è una cosa da tenere sotto controllo durante lo sviluppo di stored procedure di supporto. So che dovrò sicuramente non allenare la mia memoria muscolare dall'uso di parametri denominati.