Database
 sql >> база данни >  >> RDS >> Database

Как да не извикате съхранени процедури на Hekaton, компилирани в собствен произход

Забележка:Тази публикация първоначално е публикувана само в нашата електронна книга, Високопроизводителни техники за SQL Server, том 2. Можете да научите за нашите електронни книги тук. Също така имайте предвид, че някои от тези неща може да се променят с планираните подобрения на OLTP в паметта в SQL Server 2016.

Има някои навици и най-добри практики, които много от нас развиват с течение на времето по отношение на кода на Transact-SQL. По-специално със съхранените процедури, ние се стремим да предаваме стойности на параметри от правилния тип данни и да назоваваме параметрите си изрично, вместо да разчитаме единствено на порядковата позиция. Понякога обаче може да ни мързи за това:може да забравим да поставим префикс на Unicode низ с N , или просто избройте константите или променливите по ред, вместо да указвате имената на параметрите. Или и двете.

В SQL Server 2014, ако използвате OLTP в паметта („Hekaton“) и компилирани процедури, може да искате да коригирате малко мисленето си за тези неща. Ще демонстрирам с малко код срещу SQL Server 2014 RTM In-Memory OLTP Sample на CodePlex, който разширява примерната база данни на AdventureWorks2012. (Ако ще настроите това от нулата, за да следвате, моля, хвърлете бърз поглед върху наблюденията ми в предишна публикация.)

Нека да разгледаме подписа за съхранената процедура 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

Бях любопитен дали има значение дали параметрите са наименувани, или компилираните процедури обработват неявни преобразувания като аргументи към съхранените процедури по-добре от традиционните съхранени процедури. Първо създадох копие Sales.usp_InsertSpecialOffer_inmem като традиционна съхранена процедура – ​​това включваше просто премахване на ATOMIC блокиране и премахване на NOT NULL декларации от входните параметри:

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

За да се сведе до минимум критериите за изместване, процедурата все още се вмъква във версията In-Memory на таблицата, Sales.SpecialOffer_inmem.

След това исках да синхронизирам 100 000 извиквания към двете копия на съхранената процедура със следните критерии:

Изрично именувани параметри Параметрите не са наименувани
Всички параметри с правилен тип данни x x
Някои параметри с грешен тип данни x x


Използвайки следната партида, копирана за традиционната версия на съхранената процедура (просто премахване на _inmem от четирите EXEC обаждания):

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

Проведох всеки тест 10 пъти и ето средната продължителност в милисекунди:

Традиционна съхранена процедура
Параметри Средна продължителност
(милисекунди)
Именувани, правилни типове 72 132
Без име, правилни типове 72 846
Именувани, неправилни типове 76 154
Неименувани, неправилни типове 76 902
Естествено компилирана съхранена процедура
Параметри Средна продължителност
(милисекунди)
Именувани, правилни типове 63 202
Без име, правилни типове 61 297
Именувани, неправилни типове 64 560
Неименувани, неправилни типове 64 288

Средна продължителност в милисекунди на различни методи на извикване

С традиционната съхранена процедура е ясно, че използването на грешни типове данни оказва значително влияние върху производителността (около 4 секунди разлика), докато не назоваването на параметрите има много по-малко драматичен ефект (добавяне на около 700 ms). Винаги съм се опитвал да следвам най-добрите практики и да използвам правилните типове данни, както и да назовавам всички параметри и този малък тест изглежда потвърждава, че това може да бъде от полза.

С нативно компилираната съхранена процедура, използването на грешни типове данни все още води до подобен спад в производителността, както при традиционната съхранена процедура. Този път обаче наименуването на параметрите не помогна толкова; всъщност имаше отрицателно въздействие, добавяйки почти две секунди към общата продължителност. За да бъдем честни, това е голям брой обаждания за доста кратко време, но ако се опитвате да извлечете от тази функция абсолютно най-съвършената производителност, която можете, всяка наносекунда е от значение.

Откриване на проблема

Как можете да разберете дали компилираните ви съхранени процедури се извикват с някой от тези "бавни" методи? Има XEvent за това! Събитието се нарича natively_compiled_proc_slow_parameter_passing , и изглежда не е документирано в Books Online в момента. Можете да създадете следната сесия за разширени събития, за да наблюдавате това събитие:

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;

След като сесията започне, можете да опитате някое от горните четири повиквания поотделно и след това можете да изпълните тази заявка:

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

В зависимост от това, което сте изпълнили, трябва да видите резултати, подобни на този:

timestamp db object_id причина партида
2014-07-01 16:23:14 AdventureWorks2012 2087678485 именувани_параметри
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 преобразуване на параметър
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;

Примерни резултати от разширени събития

Надяваме се партида колоната е достатъчна, за да идентифицира виновника, но ако имате големи партиди, които съдържат множество извиквания към нативно компилирани процедури и трябва да проследите обектите, които конкретно задействат този проблем, можете просто да ги потърсите чрез object_id в съответните им бази данни.

Сега не препоръчвам да изпълнявате всички 400 000 обаждания в текста, докато сесията е активна, или да включвате тази сесия в силно едновременна производствена среда – ако правите това много, това може да доведе до значителни разходи. Много по-добре е да проверите за този вид дейност във вашата среда за разработка или етапи, стига да можете да я подложите на подходящо работно натоварване, покриващо пълен бизнес цикъл.

Заключение

Определено бях изненадан от факта, че параметрите за именуване – дълго време считани за най-добра практика – се превърнаха в най-лошата практика с компилираните в собствен произход съхранени процедури. И е известно от Microsoft, че е достатъчен потенциален проблем, че те създадоха разширено събитие, предназначено специално за проследяването му. Ако използвате In-Memory OLTP, това е едно нещо, което трябва да държите на радара си, докато разработвате поддържащи съхранени процедури. Знам, че определено ще трябва да не тренирам мускулната си памет от използването на посочени параметри.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Използване на причинно-следствената връзка за разбиране на изпълнението на заявка

  2. Разширен SQL:Вмъкнете изхода на параметризираната функция със стойност на таблица в SQL таблица

  3. Trace Flag 2389 и новият оценител на мощността

  4. NULL сложности – част 2

  5. Управление на сигурността на данните