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

Các phương pháp tốt nhất để chạy tổng - được cập nhật cho SQL Server 2012

Tôi thấy rất nhiều lời khuyên ở đó nói điều gì đó dọc theo dòng, "Thay đổi con trỏ của bạn thành một hoạt động dựa trên tập hợp; điều đó sẽ làm cho nó nhanh hơn." Mặc dù điều đó thường xảy ra nhưng không phải lúc nào nó cũng đúng. Một trường hợp sử dụng mà tôi thấy trong đó con trỏ nhiều lần làm tốt hơn cách tiếp cận dựa trên tập hợp điển hình là tính toán các tổng đang chạy. Điều này là do phương pháp dựa trên tập hợp thường phải xem xét một số phần của dữ liệu cơ bản nhiều hơn một lần, điều này có thể là một điều tồi tệ theo cấp số nhân khi dữ liệu ngày càng lớn hơn; trong khi một con trỏ - nghe có vẻ đau đớn - có thể lướt qua mỗi hàng / giá trị chính xác một lần.

Đây là các tùy chọn cơ bản của chúng tôi trong hầu hết các phiên bản SQL Server phổ biến. Tuy nhiên, trong SQL Server 2012, đã có một số cải tiến được thực hiện đối với các chức năng cửa sổ và mệnh đề OVER, chủ yếu xuất phát từ một số đề xuất tuyệt vời được gửi bởi đồng nghiệp MVP Itzik Ben-Gan (đây là một trong những đề xuất của anh ấy). Trên thực tế, Itzik có một cuốn sách MS-Press mới bao gồm tất cả những cải tiến này một cách chi tiết hơn nhiều, có tựa đề "Microsoft SQL Server 2012 T-SQL Sử dụng Chức năng Cửa sổ Hiệu suất cao".

Vì vậy, tự nhiên, tôi tò mò; chức năng tạo cửa sổ mới có làm cho kỹ thuật con trỏ và tự nối lỗi thời không? Chúng có dễ viết mã hơn không? Chúng sẽ nhanh hơn trong bất kỳ trường hợp nào (bỏ qua tất cả)? Những cách tiếp cận nào khác có thể hợp lệ?

Thiết lập

Để thực hiện một số thử nghiệm, hãy thiết lập cơ sở dữ liệu:

USE [master];
GO
IF DB_ID('RunningTotals') IS NOT NULL
BEGIN
	ALTER DATABASE RunningTotals SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
	DROP DATABASE RunningTotals;
END
GO
CREATE DATABASE RunningTotals;
GO
USE RunningTotals;
GO
SET NOCOUNT ON;
GO

Và sau đó điền vào một bảng với 10.000 hàng mà chúng ta có thể sử dụng để thực hiện một số tổng đang chạy. Không có gì quá phức tạp, chỉ cần một bảng tóm tắt với một hàng cho mỗi ngày và một con số đại diện cho bao nhiêu vé quá tốc độ đã được phát hành. Tôi đã không bị phạt quá tốc độ trong một vài năm, vì vậy tôi không biết tại sao đây là lựa chọn tiềm thức của tôi cho một mô hình dữ liệu đơn giản, nhưng nó đã có.

CREATE TABLE dbo.SpeedingTickets
(
	[Date]      DATE NOT NULL,
	TicketCount INT
);
GO
 
ALTER TABLE dbo.SpeedingTickets ADD CONSTRAINT pk PRIMARY KEY CLUSTERED ([Date]);
GO
 
;WITH x(d,h) AS
(
	SELECT TOP (250)
		ROW_NUMBER() OVER (ORDER BY [object_id]),
		CONVERT(INT, RIGHT([object_id], 2))
	FROM sys.all_objects
	ORDER BY [object_id]
)
INSERT dbo.SpeedingTickets([Date], TicketCount)
SELECT TOP (10000)
	d = DATEADD(DAY, x2.d + ((x.d-1)*250), '19831231'),
	x2.h
FROM x CROSS JOIN x AS x2
ORDER BY d;
GO
 
SELECT [Date], TicketCount
	FROM dbo.SpeedingTickets
	ORDER BY [Date];
GO

Kết quả tóm tắt:

Vì vậy, một lần nữa, 10.000 hàng dữ liệu khá đơn giản - giá trị INT nhỏ và một chuỗi ngày từ năm 1984 đến tháng 5 năm 2011.

Phương pháp tiếp cận

Bây giờ bài tập của tôi tương đối đơn giản và điển hình của nhiều ứng dụng:trả về một tập kết quả có tất cả 10.000 ngày, cùng với tổng tích lũy của tất cả các phiếu tăng tốc cho đến và bao gồm cả ngày đó. Hầu hết mọi người lần đầu tiên sẽ thử một cái gì đó như thế này (chúng tôi sẽ gọi đây là " tham gia bên trong "phương pháp):

SELECT
	st1.[Date],
	st1.TicketCount,
	RunningTotal = SUM(st2.TicketCount)
FROM
	dbo.SpeedingTickets AS st1
INNER JOIN
	dbo.SpeedingTickets AS st2
	ON st2.[Date] <= st1.[Date]
GROUP BY st1.[Date], st1.TicketCount
ORDER BY st1.[Date];

… Và bị sốc khi phát hiện ra rằng nó mất gần 10 giây để chạy. Hãy nhanh chóng kiểm tra lý do tại sao bằng cách xem kế hoạch thực thi đồ họa, sử dụng SQL Sentry Plan Explorer:

Các mũi tên to béo sẽ cho biết ngay điều gì đang xảy ra:vòng lặp lồng nhau đọc một hàng cho tập hợp đầu tiên, hai hàng cho tập hợp thứ hai, ba hàng cho tập hợp thứ ba, và tiếp tục trong toàn bộ tập hợp 10.000 hàng. Điều này có nghĩa là chúng ta sẽ thấy khoảng ((10000 * (10000 + 1)) / 2) hàng được xử lý sau khi toàn bộ tập hợp được duyệt và điều đó dường như khớp với số hàng được hiển thị trong kế hoạch.

Lưu ý rằng việc chạy truy vấn không song song (sử dụng gợi ý truy vấn TÙY CHỌN (MAXDOP 1)) làm cho hình dạng kế hoạch đơn giản hơn một chút, nhưng không giúp ích gì cả về thời gian thực thi hoặc I / O; như được hiển thị trong kế hoạch, thời lượng thực sự gần như tăng gấp đôi và số lần đọc chỉ giảm một phần trăm rất nhỏ. So với kế hoạch trước:

Có rất nhiều cách tiếp cận khác mà mọi người đã cố gắng để có được tổng số chạy hiệu quả. Một ví dụ là " phương thức truy vấn con "chỉ sử dụng một truy vấn con có tương quan theo cách giống như phương thức nối bên trong được mô tả ở trên:

SELECT
	[Date],
	TicketCount,
	RunningTotal = TicketCount + COALESCE(
	(
		SELECT SUM(TicketCount)
			FROM dbo.SpeedingTickets AS s
			WHERE s.[Date] < o.[Date]), 0
	)
FROM dbo.SpeedingTickets AS o
ORDER BY [Date];

So sánh hai kế hoạch đó:

Vì vậy, trong khi phương thức truy vấn con dường như có một kế hoạch tổng thể hiệu quả hơn, nó tệ hơn ở chỗ nó quan trọng:thời lượng và I / O. Chúng ta có thể thấy điều gì góp phần vào việc này bằng cách đi sâu vào các kế hoạch một chút. Bằng cách chuyển đến tab Hoạt động hàng đầu, chúng ta có thể thấy rằng trong phương thức nối bên trong, tìm kiếm chỉ mục được phân cụm được thực hiện 10.000 lần và tất cả các hoạt động khác chỉ được thực hiện một vài lần. Tuy nhiên, một số hoạt động được thực thi 9.999 hoặc 10.000 lần trong phương thức truy vấn con:

Vì vậy, cách tiếp cận truy vấn con có vẻ kém hơn chứ không phải tốt hơn. Phương pháp tiếp theo mà chúng tôi sẽ thử, tôi sẽ gọi là " cập nhật kỳ quặc ". Phương pháp này không được đảm bảo chính xác hoạt động và tôi sẽ không bao giờ đề xuất nó cho mã sản xuất, nhưng tôi đưa nó vào để hoàn thiện. Về cơ bản, bản cập nhật kỳ quặc lợi dụng thực tế là trong quá trình cập nhật, bạn có thể chuyển hướng bài tập và phép toán để rằng biến tăng lên ở phía sau khi mỗi hàng được cập nhật.

DECLARE @st TABLE
(
	[Date] DATE PRIMARY KEY,
	TicketCount INT,
	RunningTotal INT
);
 
DECLARE @RunningTotal INT = 0;
 
INSERT @st([Date], TicketCount, RunningTotal)
	SELECT [Date], TicketCount, RunningTotal = 0
	FROM dbo.SpeedingTickets
	ORDER BY [Date];
 
UPDATE @st
	SET @RunningTotal = RunningTotal = @RunningTotal + TicketCount
	FROM @st;
 
SELECT [Date], TicketCount, RunningTotal
	FROM @st
	ORDER BY [Date];

Tôi sẽ khẳng định lại rằng tôi không tin rằng phương pháp này an toàn cho quá trình sản xuất, bất kể lời khai bạn sẽ nghe thấy từ những người chỉ ra rằng nó "không bao giờ thất bại". Trừ khi hành vi được ghi lại và đảm bảo, tôi cố gắng tránh xa các giả định dựa trên hành vi quan sát được. Bạn không bao giờ biết khi nào một số thay đổi đối với đường dẫn quyết định của trình tối ưu hóa (dựa trên thay đổi thống kê, thay đổi dữ liệu, gói dịch vụ, cờ theo dõi, gợi ý truy vấn, bạn có gì) sẽ thay đổi đáng kể kế hoạch và có khả năng dẫn đến một thứ tự khác. Nếu bạn thực sự thích cách tiếp cận không trực quan này, bạn có thể làm cho mình cảm thấy tốt hơn một chút bằng cách sử dụng tùy chọn truy vấn LÊN LỆNH (và điều này sẽ cố gắng sử dụng quét theo thứ tự PK, vì đó là chỉ mục đủ điều kiện duy nhất trên biến bảng):

UPDATE @st
	SET @RunningTotal = RunningTotal = @RunningTotal + TicketCount
	FROM @st
	OPTION (FORCE ORDER);

Để tự tin hơn một chút với chi phí I / O cao hơn một chút, bạn có thể đưa bảng gốc hoạt động trở lại và đảm bảo rằng PK trên bảng cơ sở được sử dụng:

UPDATE st
	SET @RunningTotal = st.RunningTotal = @RunningTotal + t.TicketCount
	FROM dbo.SpeedingTickets AS t WITH (INDEX = pk)
	INNER JOIN @st AS st
	ON t.[Date] = st.[Date]
	OPTION (FORCE ORDER);

Cá nhân tôi không nghĩ rằng nó đảm bảo hơn nhiều, vì phần SET của hoạt động có thể ảnh hưởng đến trình tối ưu hóa độc lập với phần còn lại của truy vấn. Một lần nữa, tôi không đề xuất cách tiếp cận này, tôi chỉ đưa vào so sánh cho đầy đủ. Đây là kế hoạch từ truy vấn này:

Dựa trên số lần thực hiện mà chúng tôi thấy trong tab Hoạt động hàng đầu (Tôi sẽ dành cho bạn ảnh chụp màn hình; nó là 1 cho mỗi hoạt động), rõ ràng là ngay cả khi chúng tôi thực hiện liên kết để cảm thấy tốt hơn về việc đặt hàng, điều kỳ quặc cập nhật cho phép tổng số đang chạy được tính trong một lần truyền dữ liệu. So sánh nó với các truy vấn trước đó, nó hiệu quả hơn nhiều, mặc dù lần đầu tiên nó kết xuất dữ liệu vào một biến bảng và được tách ra thành nhiều thao tác:

Điều này đưa chúng ta đến " CTE đệ quy ". Phương pháp này sử dụng giá trị ngày tháng và dựa trên giả định rằng không có khoảng trống. Vì chúng tôi đã điền dữ liệu này ở trên, chúng tôi biết rằng đó là một chuỗi hoàn toàn liền kề, nhưng trong nhiều trường hợp, bạn không thể thực hiện điều đó giả định. Vì vậy, mặc dù tôi đã bao gồm nó cho đầy đủ, nhưng cách tiếp cận này không phải lúc nào cũng hợp lệ. Trong mọi trường hợp, điều này sử dụng một CTE đệ quy với ngày đầu tiên (đã biết) trong bảng làm mỏ neo và đệ quy phần được xác định bằng cách thêm một ngày (thêm tùy chọn MAXRECURSION vì chúng tôi biết chính xác mình có bao nhiêu hàng):

;WITH x AS
(
	SELECT [Date], TicketCount, RunningTotal = TicketCount
		FROM dbo.SpeedingTickets
		WHERE [Date] = '19840101'
	UNION ALL
	SELECT y.[Date], y.TicketCount, x.RunningTotal + y.TicketCount
		FROM x INNER JOIN dbo.SpeedingTickets AS y
		ON y.[Date] = DATEADD(DAY, 1, x.[Date])
)
SELECT [Date], TicketCount, RunningTotal
	FROM x
	ORDER BY [Date]
	OPTION (MAXRECURSION 10000);

Truy vấn này hoạt động hiệu quả như phương pháp cập nhật kỳ quặc. Chúng ta có thể so sánh nó với các phương thức truy vấn con và kết nối bên trong:

Giống như phương pháp cập nhật kỳ quặc, tôi sẽ không đề xuất phương pháp tiếp cận CTE này trong sản xuất trừ khi bạn hoàn toàn có thể đảm bảo rằng cột chính của bạn không có khoảng trống. Nếu bạn có thể có khoảng trống trong dữ liệu của mình, bạn có thể tạo một cái gì đó tương tự bằng cách sử dụng ROW_NUMBER (), nhưng nó sẽ không hiệu quả hơn bất kỳ phương pháp tự kết hợp nào ở trên.

Và sau đó chúng tôi có " con trỏ "cách tiếp cận:

DECLARE @st TABLE
(
	[Date]       DATE PRIMARY KEY,
	TicketCount  INT,
	RunningTotal INT
);
 
DECLARE
	@Date         DATE,
	@TicketCount  INT,
	@RunningTotal INT = 0;
 
DECLARE c CURSOR
    LOCAL STATIC FORWARD_ONLY READ_ONLY
    FOR
	SELECT [Date], TicketCount
	  FROM dbo.SpeedingTickets
	  ORDER BY [Date];
 
OPEN c;
 
FETCH NEXT FROM c INTO @Date, @TicketCount;
 
WHILE @@FETCH_STATUS = 0
BEGIN
	SET @RunningTotal = @RunningTotal + @TicketCount;
 
	INSERT @st([Date], TicketCount,  RunningTotal)
		SELECT @Date, @TicketCount, @RunningTotal;
 
	FETCH NEXT FROM c INTO @Date, @TicketCount;
END
 
CLOSE c;
DEALLOCATE c;
 
SELECT [Date], TicketCount, RunningTotal
	FROM @st
	ORDER BY [Date];

… Là nhiều mã hơn, nhưng trái với những gì quan điểm phổ biến có thể đề xuất, trả về sau 1 giây. Chúng ta có thể thấy lý do tại sao từ một số chi tiết của kế hoạch ở trên:hầu hết các phương pháp khác kết thúc việc đọc đi đọc lại cùng một dữ liệu, trong khi phương pháp con trỏ đọc mỗi hàng một lần và giữ tổng số đang chạy trong một biến thay vì tính toán tổng và hơn nữa. Chúng ta có thể thấy điều này bằng cách xem các báo cáo được thu thập bằng cách tạo một kế hoạch thực tế trong Plan Explorer:

Chúng ta có thể thấy rằng hơn 20.000 câu lệnh đã được thu thập, nhưng nếu chúng ta sắp xếp theo Hàng ước tính hoặc Hàng thực tế giảm dần, chúng ta thấy rằng chỉ có hai thao tác xử lý nhiều hơn một hàng. Khác xa với một số phương pháp ở trên gây ra số lần đọc theo cấp số nhân do đọc lặp đi lặp lại các hàng trước đó cho mỗi hàng mới.

Bây giờ, chúng ta hãy xem xét các cải tiến cửa sổ mới trong SQL Server 2012. Đặc biệt, bây giờ chúng ta có thể tính toán SUM OVER () và chỉ định một tập hợp các hàng liên quan đến hàng hiện tại. Vì vậy, ví dụ:

SELECT
	[Date],
	TicketCount,
	SUM(TicketCount) OVER (ORDER BY [Date] RANGE UNBOUNDED PRECEDING)
FROM dbo.SpeedingTickets
ORDER BY [Date];
 
SELECT
	[Date],
	TicketCount,
	SUM(TicketCount) OVER (ORDER BY [Date] ROWS UNBOUNDED PRECEDING)
FROM dbo.SpeedingTickets
ORDER BY [Date];

Hai truy vấn này xảy ra cùng một câu trả lời, với các tổng số chạy chính xác. Nhưng chúng có hoạt động giống hệt nhau không? Các kế hoạch gợi ý rằng họ không. Phiên bản với ROWS có thêm toán tử, một dự án trình tự 10.000 hàng:

Và đó là về mức độ khác biệt trong kế hoạch đồ họa. Nhưng nếu bạn nhìn kỹ hơn một chút vào các chỉ số thời gian chạy thực tế, bạn sẽ thấy những khác biệt nhỏ về thời lượng và CPU cũng như sự khác biệt rất lớn về số lần đọc. Tại sao thế này? Điều này là do RANGE sử dụng bộ đệm trên đĩa, trong khi ROWS sử dụng bộ đệm trong bộ nhớ. Với các bộ nhỏ, sự khác biệt có lẽ không đáng kể, nhưng chi phí của bộ đệm trên đĩa chắc chắn có thể trở nên rõ ràng hơn khi các bộ lớn hơn. Tôi không muốn làm hỏng phần kết, nhưng bạn có thể nghi ngờ rằng một trong những giải pháp này sẽ hoạt động tốt hơn giải pháp còn lại trong một thử nghiệm kỹ lưỡng hơn.

Ngoài ra, phiên bản sau của truy vấn mang lại kết quả tương tự, nhưng hoạt động giống như phiên bản RANGE chậm hơn ở trên:

SELECT
	[Date],
	TicketCount,
	SUM(TicketCount) OVER (ORDER BY [Date])
FROM dbo.SpeedingTickets
ORDER BY [Date];

Vì vậy, khi bạn đang chơi với các chức năng cửa sổ mới, bạn sẽ muốn ghi nhớ những mẩu tin nhỏ như thế này:phiên bản viết tắt của truy vấn hoặc phiên bản mà bạn tình cờ viết trước, không nhất thiết phải là phiên bản bạn muốn để thúc đẩy sản xuất.

Thử nghiệm thực tế

Để thực hiện các bài kiểm tra công bằng, tôi đã tạo một quy trình được lưu trữ cho từng cách tiếp cận và đo lường kết quả bằng cách thu thập các câu lệnh trên máy chủ mà tôi đã theo dõi bằng SQL Sentry (nếu bạn không sử dụng công cụ của chúng tôi, bạn có thể thu thập các sự kiện SQL:BatchCompleted theo cách tương tự bằng cách sử dụng SQL Server Profiler).

Theo "kiểm tra công bằng", tôi muốn nói rằng, ví dụ:phương pháp cập nhật kỳ quặc yêu cầu cập nhật thực tế cho dữ liệu tĩnh, có nghĩa là thay đổi lược đồ cơ bản hoặc sử dụng bảng / biến bảng tạm thời. Vì vậy, tôi đã cấu trúc các thủ tục được lưu trữ để mỗi thủ tục tạo biến bảng của riêng chúng và lưu trữ kết quả ở đó hoặc lưu trữ dữ liệu thô ở đó và sau đó cập nhật kết quả. Vấn đề khác mà tôi muốn loại bỏ là trả lại dữ liệu cho máy khách - vì vậy mỗi thủ tục có một tham số gỡ lỗi chỉ định xem không trả lại kết quả nào (mặc định), trên / dưới 5 hay tất cả. Trong các bài kiểm tra hiệu suất, tôi đặt nó để không trả về kết quả nào, nhưng tất nhiên đã xác thực từng thứ để đảm bảo rằng chúng đang trả lại kết quả phù hợp.

Tất cả các thủ tục được lưu trữ đều được mô hình hóa theo cách này (Tôi đã đính kèm một tập lệnh tạo cơ sở dữ liệu và các thủ tục được lưu trữ, vì vậy tôi chỉ đưa vào một mẫu ở đây cho ngắn gọn):

CREATE PROCEDURE [dbo].[RunningTotals_]
	@debug TINYINT = 0
	-- @debug = 1 : show top/bottom 3
	-- @debug = 2 : show all 50k
AS
BEGIN
	SET NOCOUNT ON;
 
	DECLARE @st TABLE
	(
		[Date] DATE PRIMARY KEY,
		TicketCount INT,
		RunningTotal INT
	);
 
	INSERT @st([Date], TicketCount, RunningTotal)
            -- one of seven approaches used to populate @t
 
	IF @debug = 1 -- show top 3 and last 3 to verify results
	BEGIN
		;WITH d AS
		(
			SELECT [Date], TicketCount, RunningTotal,
				rn = ROW_NUMBER() OVER (ORDER BY [Date])
				FROM @st
		)
		SELECT [Date], TicketCount, RunningTotal
			FROM d
			WHERE rn < 4 OR rn > 9997
			ORDER BY [Date];
	END
 
	IF @debug = 2 -- show all
	BEGIN
		SELECT [Date], TicketCount, RunningTotal
			FROM @st
			ORDER BY [Date];
	END
END
GO

Và tôi đã gọi cho họ theo một đợt như sau:

EXEC dbo.RunningTotals_DateCTE @debug = 0;
GO
EXEC dbo.RunningTotals_Cursor @debug = 0;
GO
EXEC dbo.RunningTotals_Subquery @debug = 0;
GO
EXEC dbo.RunningTotals_InnerJoin @debug = 0;
GO
EXEC dbo.RunningTotals_QuirkyUpdate @debug = 0;
GO
EXEC dbo.RunningTotals_Windowed_Range @debug = 0;
GO
EXEC dbo.RunningTotals_Windowed_Rows @debug = 0;
GO

Tôi nhanh chóng nhận ra rằng một số lệnh gọi này không xuất hiện trong SQL Top vì ngưỡng mặc định là 5 giây. Tôi đã thay đổi điều đó thành 100 mili giây (điều mà bạn không bao giờ muốn làm trên hệ thống sản xuất!) Như sau:

Tôi sẽ lặp lại:hành vi này không được áp dụng cho các hệ thống sản xuất!

Tôi vẫn thấy rằng một trong các lệnh trên không bị ngưỡng SQL Top; đó là phiên bản Windowed_Rows. Vì vậy, tôi chỉ thêm những thứ sau vào lô đó:

EXEC dbo.RunningTotals_Windowed_Rows @debug = 0;
WAITFOR DELAY '00:00:01';
GO

Và bây giờ tôi nhận được tất cả 7 hàng được trả về trong Top SQL. Ở đây chúng được sắp xếp theo mức sử dụng CPU giảm dần:

Bạn có thể xem thêm giây mà tôi đã thêm vào lô Windowed_Rows; nó không bị ngưỡng SQL Top vì nó hoàn thành chỉ trong 40 mili giây! Đây rõ ràng là trình hoạt động tốt nhất của chúng tôi và nếu chúng tôi có sẵn SQL Server 2012, thì đó phải là phương pháp chúng tôi sử dụng. Con trỏ cũng không tệ một nửa, xét về hiệu suất hoặc các vấn đề khác với các giải pháp còn lại. Vẽ thời lượng trên biểu đồ là khá vô nghĩa - hai điểm cao và năm điểm thấp không thể phân biệt được. Nhưng nếu I / O là nút thắt cổ chai của bạn, bạn có thể thấy việc hình dung các lần đọc rất thú vị:

Kết luận

Từ những kết quả này, chúng tôi có thể rút ra một số kết luận:

  1. Các tổng hợp được cửa sổ trong SQL Server 2012 làm cho các vấn đề về hiệu suất khi chạy các phép tính tổng (và nhiều vấn đề hàng tiếp theo / hàng trước) hiệu quả hơn một cách đáng báo động. Khi tôi thấy số lần đọc thấp, tôi nghĩ chắc chắn có một sự nhầm lẫn nào đó, rằng tôi hẳn đã quên thực hiện bất kỳ công việc nào. Nhưng không, bạn sẽ nhận được cùng một số lần đọc nếu thủ tục được lưu trữ của bạn chỉ thực hiện một SELECT thông thường từ bảng SpeedingTickets. (Vui lòng tự kiểm tra điều này với STATISTICS IO.)
  2. Các vấn đề tôi đã chỉ ra trước đó về RANGE so với ROWS mang lại thời gian chạy hơi khác nhau (chênh lệch thời lượng khoảng 6 lần - hãy nhớ bỏ qua giây tôi đã thêm với WAITFOR), nhưng sự khác biệt về số đọc là thiên văn do bộ đệm trên đĩa. Nếu tổng hợp được cửa sổ của bạn có thể được giải quyết bằng ROWS, hãy tránh RANGE, nhưng bạn nên kiểm tra xem cả hai đều cho cùng một kết quả (hoặc ít nhất ROWS đưa ra câu trả lời đúng). Bạn cũng nên lưu ý rằng nếu bạn đang sử dụng một truy vấn tương tự và bạn không chỉ định RANGE hay ROWS, kế hoạch sẽ hoạt động như thể bạn đã chỉ định RANGE).
  3. Các phương thức truy vấn con và kết nối bên trong tương đối phức tạp. 35 giây đến một phút để tạo ra các tổng số đang chạy này? Và đây là trên một chiếc bàn đơn lẻ, mỏng manh mà không trả kết quả cho khách hàng. Những so sánh này có thể được sử dụng để cho mọi người thấy lý do tại sao một giải pháp hoàn toàn dựa trên bộ không phải lúc nào cũng là câu trả lời tốt nhất.
  4. Trong số các cách tiếp cận nhanh hơn, giả sử bạn chưa sẵn sàng cho SQL Server 2012 và giả sử bạn loại bỏ cả phương pháp cập nhật kỳ quặc (không được hỗ trợ) và phương pháp ngày CTE (không thể đảm bảo trình tự liền kề), chỉ con trỏ thực hiện có thể chấp nhận được. Nó có thời lượng cao nhất trong số các giải pháp "nhanh hơn", nhưng lại có ít lượt đọc nhất.

Tôi hy vọng những bài kiểm tra này sẽ giúp đánh giá cao hơn những cải tiến về cửa sổ mà Microsoft đã thêm vào SQL Server 2012. Hãy nhớ cảm ơn Itzik nếu bạn thấy anh ấy trực tuyến hoặc gặp trực tiếp, vì anh ấy là động lực đằng sau những thay đổi này. Ngoài ra, tôi hy vọng điều này sẽ giúp mở ra một số tâm trí ngoài kia rằng con trỏ có thể không phải lúc nào cũng là giải pháp xấu xa và đáng sợ mà nó thường được mô tả.

(Như một phần phụ lục, tôi đã kiểm tra chức năng CLR do Pavel Pawlowski cung cấp và các đặc tính hiệu suất gần giống với giải pháp SQL Server 2012 sử dụng ROWS. Số lần đọc giống hệt nhau, CPU là 78 ​​so với 47 và thời lượng tổng thể là 73 thay vì 40. Vì vậy, nếu bạn sẽ không chuyển sang SQL Server 2012 trong tương lai gần, bạn có thể muốn thêm giải pháp của Pavel vào các thử nghiệm của mình.)

Tệp đính kèm:RunningTotals_Demo.sql.zip (2kb)


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Làm thế nào để chèn nhiều bản ghi và nhận giá trị nhận dạng?

  2. Làm cách nào để lấy ngày ở định dạng YYYY-MM-DD từ trường ngày giờ TSQL?

  3. Chuyển bảng dưới dạng tham số vào UDF máy chủ sql

  4. Tách các giá trị được phân tách trong một cột SQL thành nhiều hàng

  5. Phương pháp tốt nhất để chèn bản ghi nếu bản ghi đó chưa tồn tại là gì?