Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Cách không gọi các thủ tục lưu trữ được biên dịch tự nhiên của Hekaton

Lưu ý:Bài đăng này ban đầu chỉ được xuất bản trong sách điện tử của chúng tôi, Kỹ thuật hiệu suất cao cho SQL Server, Tập 2. Bạn có thể tìm hiểu về sách điện tử của chúng tôi tại đây. Cũng lưu ý rằng một số điều này có thể thay đổi với các cải tiến đã lên kế hoạch cho OLTP trong bộ nhớ trong SQL Server 2016.

Có một số thói quen và phương pháp hay nhất mà nhiều người trong chúng ta phát triển theo thời gian liên quan đến mã Transact-SQL. Đặc biệt, với các thủ tục được lưu trữ, chúng tôi cố gắng chuyển các giá trị tham số của kiểu dữ liệu chính xác và đặt tên cho các tham số của chúng tôi một cách rõ ràng thay vì chỉ dựa vào vị trí thứ tự. Tuy nhiên, đôi khi chúng ta có thể lười biếng về điều này:chúng ta có thể quên thêm tiền tố chuỗi Unicode bằng N hoặc chỉ liệt kê các hằng số hoặc biến theo thứ tự thay vì chỉ định tên tham số. Hoặc cả hai.

Trong SQL Server 2014, nếu bạn đang sử dụng OLTP trong bộ nhớ ("Hekaton") và các thủ tục được biên dịch tự nhiên, bạn có thể muốn điều chỉnh suy nghĩ của mình về những điều này một chút. Tôi sẽ chứng minh bằng một số mã dựa trên Mẫu OLTP trong bộ nhớ SQL Server 2014 RTM trên CodePlex, mở rộng cơ sở dữ liệu mẫu AdventureWorks2012. (Nếu bạn định thiết lập điều này từ đầu để làm theo, vui lòng xem nhanh các quan sát của tôi trong bài viết trước.)

Hãy xem chữ ký cho quy trình được lưu trữ 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

Tôi tò mò liệu nó có quan trọng hay không nếu các tham số được đặt tên, hoặc nếu các thủ tục được biên dịch tự nhiên xử lý các chuyển đổi ngầm định làm đối số cho các thủ tục được lưu trữ tốt hơn so với các thủ tục được lưu trữ truyền thống. Đầu tiên, tôi đã tạo một bản sao Sales.usp_InsertSpecialOffer_inmem như một thủ tục được lưu trữ truyền thống - điều này chỉ liên quan đến việc xóa ATOMIC chặn và xóa NOT NULL khai báo từ các tham số đầu vào:

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

Để giảm thiểu tiêu chí thay đổi, quy trình vẫn chèn vào phiên bản Trong bộ nhớ của bảng, Sales.SpecialOffer_inmem.

Sau đó, tôi muốn định thời gian cho 100.000 cuộc gọi tới cả hai bản sao của thủ tục được lưu trữ với các tiêu chí sau:

Các thông số được đặt tên rõ ràng Các tham số không được đặt tên
Tất cả các tham số của kiểu dữ liệu chính xác x x
Một số tham số của kiểu dữ liệu sai x x


Sử dụng lô sau, được sao chép cho phiên bản truyền thống của quy trình được lưu trữ (chỉ cần xóa _inmem từ bốn EXEC cuộc gọi):

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

Tôi đã chạy mỗi bài kiểm tra 10 lần và đây là thời lượng trung bình, tính bằng mili giây:

Quy trình lưu trữ truyền thống
Tham số Thời lượng trung bình
(mili giây)
Các loại được đặt tên, thích hợp 72.132
Không có tên, loại phù hợp 72.846
Loại được đặt tên, không phù hợp 76.154
Không có tên, không đúng loại 76,902
Quy trình được lưu trữ được biên dịch theo hướng tổng hợp
Tham số Thời lượng trung bình
(mili giây)
Các loại được đặt tên, thích hợp 63,202
Không có tên, loại phù hợp 61.297
Loại được đặt tên, không phù hợp 64.560
Không có tên, không đúng loại 64.288

Thời lượng trung bình, tính bằng mili giây, của các phương thức gọi khác nhau

Với quy trình lưu trữ truyền thống, rõ ràng là việc sử dụng sai kiểu dữ liệu có tác động đáng kể đến hiệu suất (chênh lệch khoảng 4 giây), trong khi việc không đặt tên cho các tham số có tác động ít hơn nhiều (thêm khoảng 700ms). Tôi luôn cố gắng làm theo các phương pháp hay nhất và sử dụng các loại dữ liệu phù hợp cũng như đặt tên cho tất cả các tham số và thử nghiệm nhỏ này dường như xác nhận rằng làm như vậy có thể có lợi.

Với thủ tục được lưu trữ được biên dịch nguyên bản, việc sử dụng sai kiểu dữ liệu vẫn dẫn đến sự sụt giảm hiệu suất tương tự như với thủ tục được lưu trữ truyền thống. Tuy nhiên, lần này, việc đặt tên cho các tham số không giúp được gì nhiều; trên thực tế, nó có tác động tiêu cực, thêm gần hai giây vào thời lượng tổng thể. Công bằng mà nói, đây là một số lượng lớn các cuộc gọi trong thời gian khá ngắn, nhưng nếu bạn đang cố gắng đạt được hiệu suất vượt trội nhất tuyệt đối thì bạn có thể sử dụng tính năng này, mỗi nano giây đều có giá trị.

Khám phá vấn đề

Làm thế nào bạn có thể biết liệu các thủ tục được lưu trữ được biên dịch tự nhiên của bạn có được gọi bằng một trong các phương thức "chậm" này hay không? Có một XEvent cho điều đó! Sự kiện được gọi là naately_compiled_proc_slow_parameter_passing và dường như nó không được ghi lại trong Sách Trực tuyến vào lúc này. Bạn có thể tạo phiên Sự kiện mở rộng sau để theo dõi sự kiện này:

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;

Khi phiên đang chạy, bạn có thể thử riêng lẻ bất kỳ lệnh gọi nào trong số bốn lệnh trên, sau đó bạn có thể chạy truy vấn này:

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

Tùy thuộc vào những gì bạn đã chạy, bạn sẽ thấy kết quả tương tự như sau:

dấu thời gian db object_id lý do
2014-07-01 16:23:14 AdventureWorks2012 2087678485 names_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;

Kết quả mẫu từ Sự kiện mở rộng

Hy vọng rằng cột là đủ để xác định thủ phạm, nhưng nếu bạn có các lô lớn chứa nhiều lệnh gọi đến các thủ tục được biên dịch tự nhiên và bạn cần theo dõi các đối tượng gây ra sự cố này một cách cụ thể, bạn có thể chỉ cần tra cứu chúng bằng object_id trong cơ sở dữ liệu tương ứng của họ.

Bây giờ, tôi không khuyên bạn nên chạy tất cả 400.000 cuộc gọi trong văn bản trong khi phiên đang hoạt động hoặc bật phiên này trong môi trường sản xuất, đồng thời cao - nếu bạn làm điều này nhiều, nó có thể gây ra một số chi phí đáng kể. Tốt hơn hết là bạn nên kiểm tra loại hoạt động này trong môi trường phát triển hoặc môi trường tổ chức của mình, miễn là bạn có thể đặt nó vào một khối lượng công việc phù hợp bao gồm toàn bộ chu kỳ kinh doanh.

Kết luận

Tôi chắc chắn rất ngạc nhiên bởi thực tế là việc đặt tên cho các tham số - từ lâu được coi là một phương pháp hay nhất - đã bị biến thành một phương pháp tồi tệ nhất với các thủ tục được lưu trữ được biên dịch nguyên bản. Và Microsoft biết rằng họ đã tạo ra một Sự kiện mở rộng được thiết kế đặc biệt để theo dõi vấn đề tiềm ẩn. Nếu bạn đang sử dụng OLTP trong bộ nhớ, đây là một điều bạn nên lưu ý khi phát triển các thủ tục được lưu trữ hỗ trợ. Tôi biết tôi chắc chắn sẽ phải rèn luyện trí nhớ cơ bắp của mình bằng cách sử dụng các thông số được đặt tên.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Tạo và triển khai nhiều phiên bản cơ sở dữ liệu thông qua ảnh chụp nhanh lược đồ

  2. Làm thế nào để đổi tên một tên cột trong SQL?

  3. TRƯỜNG HỢP SQL:Biết và Tránh 3 rắc rối ít được biết đến

  4. Làm thế nào để truy xuất một tập hợp các ký tự bằng cách sử dụng SUBSTRING trong SQL?

  5. Cách cài đặt Apache Cassandra trên Ubuntu 20.10 / Ubuntu 20.04