sql >> Databáze >  >> RDS >> Database

Jak nevolat Hekaton nativně kompilované uložené procedury

Poznámka:Tento příspěvek byl původně publikován pouze v naší elektronické knize, High Performance Techniques for SQL Server, Volume 2. O našich elektronických knihách se můžete dozvědět zde. Všimněte si také, že některé z těchto věcí se mohou změnit s plánovanými vylepšeními In-Memory OLTP v SQL Server 2016.

Existuje několik zvyků a osvědčených postupů, které si mnoho z nás časem vyvine s ohledem na kód Transact-SQL. Zejména u uložených procedur se snažíme předávat hodnoty parametrů správného datového typu a pojmenovávat naše parametry explicitně, spíše než se spoléhat pouze na řadovou pozici. Někdy však můžeme být líní v tomto:můžeme zapomenout předponu řetězce Unicode s N , nebo pouze seznam konstant nebo proměnných v pořadí namísto zadávání názvů parametrů. Nebo obojí.

Pokud v SQL Server 2014 používáte In-Memory OLTP ("Hekaton") a nativně zkompilované procedury, možná budete chtít své myšlení o těchto věcech trochu upravit. Předvedu nějaký kód na ukázce SQL Server 2014 RTM In-Memory OLTP Sample na CodePlex, která rozšiřuje ukázkovou databázi AdventureWorks2012. (Pokud to chcete nastavit od začátku, abyste mohli pokračovat, podívejte se prosím rychle na moje pozorování v předchozím příspěvku.)

Podívejme se na podpis pro uloženou proceduru 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

Zajímalo by mě, jestli záleží na tom, jestli jsou parametry pojmenované, nebo jestli nativně kompilované procedury zvládají implicitní převody jako argumenty pro uložené procedury lépe než tradiční uložené procedury. Nejprve jsem vytvořil kopii Sales.usp_InsertSpecialOffer_inmem jako tradiční uložená procedura – to zahrnovalo pouze odstranění ATOMIC blokovat a odstranit NOT NULL deklarace ze vstupních parametrů:

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

Aby se minimalizovalo přesouvání kritérií, postup se stále vkládá do verze tabulky v paměti Sales.SpecialOffer_inmem.

Potom jsem chtěl načasovat 100 000 volání do obou kopií uložené procedury s těmito kritérii:

Výslovně pojmenované parametry Parametry nejsou pojmenovány
Všechny parametry správného datového typu x x
Některé parametry nesprávného datového typu x x


Pomocí následující dávky, zkopírované pro tradiční verzi uložené procedury (stačí odstranit _inmem ze čtyř EXEC hovory):

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

Každý test jsem provedl 10krát a zde byly průměrné doby trvání v milisekundách:

Tradiční uložená procedura
Parametry Průměrná doba trvání
(milisekundy)
Pojmenované, správné typy 72 132
Bez názvu, správné typy 72 846
Pojmenované, nevhodné typy 76 154
Bez názvu, nevhodné typy 76 902
Nativně zkompilovaná uložená procedura
Parametry Průměrná doba trvání
(milisekundy)
Pojmenované, správné typy 63 202
Bez názvu, správné typy 61 297
Pojmenované, nevhodné typy 64 560
Bez názvu, nevhodné typy 64 288

Průměrná doba trvání různých metod volání v milisekundách

U tradiční uložené procedury je jasné, že použití nesprávných datových typů má podstatný dopad na výkon (asi 4 sekundový rozdíl), zatímco nepojmenování parametrů mělo mnohem méně dramatický efekt (přidání asi 700 ms). Vždy jsem se snažil dodržovat osvědčené postupy a používat správné datové typy a také pojmenovávat všechny parametry a zdá se, že tento malý test potvrzuje, že to může být prospěšné.

U nativně zkompilované uložené procedury vedlo použití nesprávných datových typů stále k podobnému poklesu výkonu jako u tradiční uložené procedury. Tentokrát však pojmenování parametrů tolik nepomohlo; ve skutečnosti to mělo negativní dopad a přidalo téměř dvě sekundy k celkovému trvání. Abychom byli spravedliví, jedná se o velký počet hovorů za poměrně krátkou dobu, ale pokud se z této funkce snažíte vymáčknout absolutně nejproblematičtější výkon, počítá se každá nanosekunda.

Odhalení problému

Jak můžete vědět, zda jsou vaše nativně zkompilované uložené procedury volány některou z těchto „pomalých“ metod? Na to je XEvent! Událost se nazývá native_compiled_proc_slow_parameter_passing a nezdá se, že by to v současnosti bylo zdokumentováno v Books Online. Pro sledování této události můžete vytvořit následující relaci Extended Events:

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;

Jakmile relace běží, můžete jednotlivě vyzkoušet kterékoli z výše uvedených čtyř volání a poté můžete spustit tento dotaz:

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

V závislosti na tom, co jste spustili, byste měli vidět výsledky podobné tomuto:

časové razítko db id_objektu důvod dávka
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;

Ukázkové výsledky z rozšířených událostí

Doufejme, že dávka sloupec stačí k identifikaci viníka, ale pokud máte velké dávky, které obsahují více volání nativně kompilovaných procedur a potřebujete vysledovat objekty, které konkrétně spouštějí tento problém, můžete je jednoduše vyhledat pomocí id_objektu v jejich příslušných databázích.

Nyní nedoporučuji spouštět všech 400 000 hovorů v textu, když je relace aktivní, nebo tuto relaci zapínat ve vysoce souběžném produkčním prostředí – pokud to děláte hodně, může to způsobit značnou režii. Je pro vás mnohem lepší kontrolovat tento druh činnosti ve vašem vývojovém nebo přípravném prostředí, pokud je můžete podrobit řádné pracovní zátěži pokrývající celý obchodní cyklus.

Závěr

Rozhodně mě překvapila skutečnost, že pojmenování parametrů – dlouho považované za osvědčený postup – se u nativně kompilovaných uložených procedur změnilo v nejhorší. A Microsoft ví, že potenciální problém je dostatečný, že vytvořili Extended Event navrženou speciálně k jejímu sledování. Pokud používáte In-Memory OLTP, toto je jedna věc, kterou byste měli mít na paměti, když vyvíjíte podpůrné uložené procedury. Vím, že rozhodně budu muset odtrénovat svou svalovou paměť používáním pojmenovaných parametrů.


  1. Jak funguje poddotaz v příkazu select v oracle

  2. Jak přenést všechny databáze MySQL ze starého na nový server

  3. Změňte uživatelsky definovaný typ v SQL Server

  4. Jak generovat skripty pro opětovné vytvoření omezení cizích klíčů v databázi SQL Server - SQL Server / Výukový program TSQL, část 73