Database
 sql >> Baza danych >  >> RDS >> Database

Jak nie wywoływać natywnie skompilowanych procedur składowanych Hekaton

Uwaga:ten post został pierwotnie opublikowany tylko w naszym eBooku, Techniki wysokiej wydajności dla SQL Server, tom 2. Możesz dowiedzieć się o naszych eBookach tutaj. Należy również pamiętać, że niektóre z tych rzeczy mogą ulec zmianie wraz z planowanymi ulepszeniami OLTP w pamięci w SQL Server 2016.

Istnieje kilka nawyków i najlepszych praktyk, które wielu z nas rozwija z biegiem czasu w odniesieniu do kodu Transact-SQL. W szczególności w przypadku procedur składowanych staramy się przekazywać wartości parametrów o poprawnym typie danych i jawnie nazywać nasze parametry, zamiast polegać wyłącznie na pozycji porządkowej. Czasami jednak możemy być leniwi:możemy zapomnieć o prefiksie ciągu Unicode przed N lub po prostu wymień stałe lub zmienne w kolejności zamiast określania nazw parametrów. Albo jedno i drugie.

W SQL Server 2014, jeśli używasz In-Memory OLTP ("Hekaton") i natywnie kompilowanych procedur, możesz chcieć nieco dostosować swoje myślenie o tych rzeczach. Zademonstruję z pewnym kodem na przykładzie SQL Server 2014 RTM In-Memory OLTP na CodePlex, który rozszerza przykładową bazę danych AdventureWorks2012. (Jeśli masz zamiar skonfigurować to od zera, aby kontynuować, proszę rzucić okiem na moje obserwacje w poprzednim poście.)

Przyjrzyjmy się podpisowi procedury składowanej 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

Byłem ciekaw, czy ma znaczenie, czy parametry zostały nazwane, czy natywnie skompilowane procedury obsługiwały niejawne konwersje jako argumenty procedur składowanych lepiej niż tradycyjne procedury składowane. Najpierw utworzyłem kopię Sales.usp_InsertSpecialOffer_inmem jako tradycyjna procedura składowana – wymagało to jedynie usunięcia ATOMIC blokowanie i usuwanie NOT NULL deklaracje z parametrów wejściowych:

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 zminimalizować zmiany kryteriów, procedura nadal jest wstawiana do wersji tabeli w pamięci, Sales.SpecialOffer_inmem.

Następnie chciałem zsynchronizować 100 000 wywołań obu kopii procedury składowanej z następującymi kryteriami:

Parametry wyraźnie nazwane Parametry nienazwane
Wszystkie parametry prawidłowego typu danych x x
Niektóre parametry o niewłaściwym typie danych x x


Korzystając z następującej partii, skopiowanej dla tradycyjnej wersji procedury składowanej (po prostu usuwając _inmem z czterech EXEC połączenia):

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

Przeprowadziłem każdy test 10 razy, a oto średnie czasy trwania w milisekundach:

Tradycyjna procedura składowana
Parametry Średni czas trwania
(milisekundy)
Nazwane, właściwe typy 72 132
Nie nazwane, właściwe typy 72 846
Nazwane, niewłaściwe typy 76154
Nie nazwane, niewłaściwe typy 76 902
Natywnie skompilowana procedura składowana
Parametry Średni czas trwania
(milisekundy)
Nazwane, właściwe typy 63 202
Nie nazwane, właściwe typy 61 297
Nazwane, niewłaściwe typy 64 560
Nie nazwane, niewłaściwe typy 64 288

Średni czas trwania w milisekundach różnych metod wywoływania

W przypadku tradycyjnej procedury składowanej jasne jest, że użycie niewłaściwych typów danych ma znaczny wpływ na wydajność (około 4 sekund różnicy), podczas gdy brak nazwy parametrów miał znacznie mniej dramatyczny efekt (dodanie około 700 ms). Zawsze starałem się postępować zgodnie z najlepszymi praktykami i używać właściwych typów danych, a także nazywać wszystkie parametry, a ten mały test wydaje się potwierdzać, że może to być korzystne.

W przypadku natywnie skompilowanej procedury składowanej użycie niewłaściwych typów danych nadal prowadziło do podobnego spadku wydajności, jak w przypadku tradycyjnej procedury składowanej. Tym razem jednak nazwanie parametrów nie pomogło zbytnio; w rzeczywistości miało to negatywny wpływ, dodając prawie dwie sekundy do całkowitego czasu trwania. Szczerze mówiąc, jest to duża liczba połączeń w dość krótkim czasie, ale jeśli próbujesz wycisnąć z tej funkcji absolutnie najlepszą wydajność, jaką możesz, liczy się każda nanosekunda.

Odkrywanie problemu

Skąd możesz wiedzieć, czy natywnie skompilowane procedury składowane są wywoływane za pomocą jednej z tych „wolnych” metod? Jest na to XEvent! Zdarzenie nazywa się natively_compiled_proc_slow_parameter_passing i wydaje się, że obecnie nie jest to udokumentowane w Books Online. Możesz utworzyć następującą sesję zdarzeń rozszerzonych, aby monitorować to zdarzenie:

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;

Po uruchomieniu sesji możesz wypróbować dowolne z powyższych czterech wywołań pojedynczo, a następnie uruchomić to zapytanie:

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

W zależności od tego, co uruchomiłeś, powinieneś zobaczyć wyniki podobne do tego:

sygnatura czasowa db identyfikator_obiektu powód partia
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;

Przykładowe wyniki z wydarzeń rozszerzonych

Mam nadzieję, że partia kolumna jest wystarczająca do zidentyfikowania winowajcy, ale jeśli masz duże partie, które zawierają wiele wywołań do natywnie skompilowanych procedur i musisz wyśledzić obiekty, które konkretnie wywołują ten problem, możesz je po prostu wyszukać za pomocą object_id w odpowiednich bazach danych.

Teraz nie polecam uruchamiania wszystkich 400 000 wywołań w tekście, gdy sesja jest aktywna, ani włączania tej sesji w wysoce współbieżnym środowisku produkcyjnym — jeśli robisz to dużo, może to spowodować znaczne obciążenie. O wiele lepiej jest sprawdzić tego rodzaju aktywność w środowisku programistycznym lub pomostowym, o ile możesz poddać ją odpowiedniemu obciążeniu obejmującemu pełny cykl biznesowy.

Wniosek

Zdecydowanie zaskoczył mnie fakt, że nazewnictwo parametrów – od dawna uważane za najlepszą praktykę – zostało zamienione w najgorszą praktykę przy natywnie kompilowanych procedurach składowanych. Microsoft wie, że jest wystarczającym potencjalnym problemem, że stworzyli rozszerzone zdarzenie zaprojektowane specjalnie do jego śledzenia. Jeśli używasz protokołu OLTP w pamięci, jest to jedna rzecz, którą powinieneś mieć na swoim radarze podczas opracowywania wspierających procedur przechowywanych. Wiem, że zdecydowanie będę musiał wytrenować pamięć mięśniową od używania nazwanych parametrów.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Typ danych SQL VARCHAR Zalecenia i zakazy dla szybszych baz danych

  2. Wprowadzenie do przetwarzania asynchronicznego za pomocą Service Broker

  3. Jak upewnić się, że bazy danych nie mają pofragmentowanych indeksów

  4. Wprowadzenie do HDFS | Co to jest HDFS i jak to działa?

  5. Jak obliczyć współczynnik retencji w SQL?