Đầu tiên, tôi xin lỗi vì sự chậm trễ trong phản hồi của tôi kể từ những bình luận cuối cùng của tôi.
Chủ đề đưa ra trong các ý kiến rằng sử dụng CTE đệ quy (rCTE từ đây trở đi) chạy đủ nhanh vì số lượng hàng thấp. Trong khi nó có thể xuất hiện theo cách đó, không có gì có thể hơn từ sự thật.
BUILD TALLY BẢNG VÀ CHỨC NĂNG TALLY
Trước khi bắt đầu thử nghiệm, chúng tôi cần xây dựng Bảng Tally vật lý với Chỉ số cụm phù hợp và Chức năng Tally theo kiểu Itzik Ben-Gan. Chúng tôi cũng sẽ thực hiện tất cả những điều này trong TempDB để chúng tôi không vô tình bỏ rơi những món quà của bất kỳ ai.
Đây là mã để xây dựng Bảng Tally và phiên bản sản xuất tuyệt vời của mã Itzik hiện tại của tôi.
--===== Do this in a nice, safe place that everyone has
USE tempdb
;
--===== Create/Recreate a Physical Tally Table
IF OBJECT_ID('dbo.Tally','U') IS NOT NULL
DROP TABLE dbo.Tally
;
-- Note that the ISNULL makes a NOT NULL column
SELECT TOP 1000001
N = ISNULL(ROW_NUMBER() OVER (ORDER BY (SELECT NULL))-1,0)
INTO dbo.Tally
FROM sys.all_columns ac1
CROSS JOIN sys.all_columns ac2
;
ALTER TABLE dbo.Tally
ADD CONSTRAINT PK_Tally PRIMARY KEY CLUSTERED (N)
;
--===== Create/Recreate a Tally Function
IF OBJECT_ID('dbo.fnTally','IF') IS NOT NULL
DROP FUNCTION dbo.fnTally
;
GO
CREATE FUNCTION [dbo].[fnTally]
/**********************************************************************************************************************
Purpose:
Return a column of BIGINTs from @ZeroOrOne up to and including @MaxN with a max value of 1 Trillion.
As a performance note, it takes about 00:02:10 (hh:mm:ss) to generate 1 Billion numbers to a throw-away variable.
Usage:
--===== Syntax example (Returns BIGINT)
SELECT t.N
FROM dbo.fnTally(@ZeroOrOne,@MaxN) t
;
Notes:
1. Based on Itzik Ben-Gan's cascading CTE (cCTE) method for creating a "readless" Tally Table source of BIGINTs.
Refer to the following URLs for how it works and introduction for how it replaces certain loops.
http://www.sqlservercentral.com/articles/T-SQL/62867/
http://sqlmag.com/sql-server/virtual-auxiliary-table-numbers
2. To start a sequence at 0, @ZeroOrOne must be 0 or NULL. Any other value that's convertable to the BIT data-type
will cause the sequence to start at 1.
3. If @ZeroOrOne = 1 and @MaxN = 0, no rows will be returned.
5. If @MaxN is negative or NULL, a "TOP" error will be returned.
6. @MaxN must be a positive number from >= the value of @ZeroOrOne up to and including 1 Billion. If a larger
number is used, the function will silently truncate after 1 Billion. If you actually need a sequence with
that many values, you should consider using a different tool. ;-)
7. There will be a substantial reduction in performance if "N" is sorted in descending order. If a descending
sort is required, use code similar to the following. Performance will decrease by about 27% but it's still
very fast especially compared with just doing a simple descending sort on "N", which is about 20 times slower.
If @ZeroOrOne is a 0, in this case, remove the "+1" from the code.
DECLARE @MaxN BIGINT;
SELECT @MaxN = 1000;
SELECT DescendingN = @MaxN-N+1
FROM dbo.fnTally(1,@MaxN);
8. There is no performance penalty for sorting "N" in ascending order because the output is explicity sorted by
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
Revision History:
Rev 00 - Unknown - Jeff Moden
- Initial creation with error handling for @MaxN.
Rev 01 - 09 Feb 2013 - Jeff Moden
- Modified to start at 0 or 1.
Rev 02 - 16 May 2013 - Jeff Moden
- Removed error handling for @MaxN because of exceptional cases.
Rev 03 - 22 Apr 2015 - Jeff Moden
- Modify to handle 1 Trillion rows for experimental purposes.
**********************************************************************************************************************/
(@ZeroOrOne BIT, @MaxN BIGINT)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN WITH
E1(N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1) --10E1 or 10 rows
, E4(N) AS (SELECT 1 FROM E1 a, E1 b, E1 c, E1 d) --10E4 or 10 Thousand rows
,E12(N) AS (SELECT 1 FROM E4 a, E4 b, E4 c) --10E12 or 1 Trillion rows
SELECT N = 0 WHERE ISNULL(@ZeroOrOne,0)= 0 --Conditionally start at 0.
UNION ALL
SELECT TOP(@MaxN) N = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E12 -- Values from 1 to @MaxN
;
GO
Nhân tiện ... lưu ý rằng đã xây dựng Bảng Tally một triệu và một hàng và thêm Chỉ mục cụm vào đó trong khoảng một giây. Hãy thử THAT với một rCTE và xem mất bao lâu! ;-)
XÂY DỰNG MỘT SỐ DỮ LIỆU KIỂM TRA
Chúng tôi cũng cần một số dữ liệu thử nghiệm. Có, tôi đồng ý rằng tất cả các chức năng mà chúng tôi sẽ kiểm tra, bao gồm rCTE, chạy trong một phần nghìn giây hoặc ít hơn chỉ 12 hàng nhưng đó là cái bẫy mà rất nhiều người rơi vào. Chúng ta sẽ nói nhiều hơn về cái bẫy đó sau, nhưng bây giờ, hãy mô phỏng việc gọi mỗi chức năng 40.000 lần, tức là khoảng bao nhiêu lần một số chức năng nhất định trong cửa hàng của tôi được gọi trong 8 ngày. Chỉ cần tưởng tượng có bao nhiêu lần các chức năng như vậy có thể được gọi trong một doanh nghiệp bán lẻ trực tuyến lớn.
Vì vậy, đây là mã để xây dựng 40.000 hàng với ngày ngẫu nhiên, mỗi hàng có Số hàng chỉ dành cho mục đích theo dõi. Tôi đã không dành thời gian để làm cho thời gian cả giờ vì nó không quan trọng ở đây.
--===== Do this in a nice, safe place that everyone has
USE tempdb
;
--===== Create/Recreate a Test Date table
IF OBJECT_ID('dbo.TestDate','U') IS NOT NULL
DROP TABLE dbo.TestDate
;
DECLARE @StartDate DATETIME
,@EndDate DATETIME
,@Rows INT
;
SELECT @StartDate = '2010' --Inclusive
,@EndDate = '2020' --Exclusive
,@Rows = 40000 --Enough to simulate an 8 hour day where I work
;
SELECT RowNum = IDENTITY(INT,1,1)
,SomeDateTime = RAND(CHECKSUM(NEWID()))*DATEDIFF(dd,@StartDate,@EndDate)+@StartDate
INTO dbo.TestDate
FROM dbo.fnTally(1,@Rows)
;
XÂY DỰNG MỘT SỐ CHỨC NĂNG ĐỂ LÀM 12 GIỜ
Tiếp theo, tôi đã chuyển đổi mã rCTE thành một hàm và tạo 3 hàm khác. Tất cả chúng đều được tạo ra dưới dạng iTVF hiệu suất cao (Hàm nội tuyến có giá trị bảng). Bạn luôn có thể nói vì iTVF không bao giờ có BEGIN trong đó như vô hướng hoặc mTVF (Hàm đa giá trị bảng tuyên bố).
Đây là mã để xây dựng 4 hàm đó ... Tôi đặt tên cho chúng theo phương thức chúng sử dụng chứ không phải những gì chúng làm chỉ để dễ xác định chúng hơn.
--===== CREATE THE iTVFs
--===== Do this in a nice, safe place that everyone has
USE tempdb
;
-----------------------------------------------------------------------------------------
IF OBJECT_ID('dbo.OriginalrCTE','IF') IS NOT NULL
DROP FUNCTION dbo.OriginalrCTE
;
GO
CREATE FUNCTION dbo.OriginalrCTE
(@Date DATETIME)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
WITH Dates AS
(
SELECT DATEPART(HOUR,DATEADD(HOUR,-1,@Date)) [Hour],
DATEADD(HOUR,-1,@Date) [Date], 1 Num
UNION ALL
SELECT DATEPART(HOUR,DATEADD(HOUR,-1,[Date])),
DATEADD(HOUR,-1,[Date]), Num+1
FROM Dates
WHERE Num <= 11
)
SELECT [Hour], [Date]
FROM Dates
GO
-----------------------------------------------------------------------------------------
IF OBJECT_ID('dbo.MicroTally','IF') IS NOT NULL
DROP FUNCTION dbo.MicroTally
;
GO
CREATE FUNCTION dbo.MicroTally
(@Date DATETIME)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
SELECT [Hour] = DATEPART(HOUR,DATEADD(HOUR,t.N,@Date))
,[DATE] = DATEADD(HOUR,t.N,@Date)
FROM (VALUES (-1),(-2),(-3),(-4),(-5),(-6),(-7),(-8),(-9),(-10),(-11),(-12))t(N)
;
GO
-----------------------------------------------------------------------------------------
IF OBJECT_ID('dbo.PhysicalTally','IF') IS NOT NULL
DROP FUNCTION dbo.PhysicalTally
;
GO
CREATE FUNCTION dbo.PhysicalTally
(@Date DATETIME)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
SELECT [Hour] = DATEPART(HOUR,DATEADD(HOUR,-t.N,@Date))
,[DATE] = DATEADD(HOUR,-t.N,@Date)
FROM dbo.Tally t
WHERE N BETWEEN 1 AND 12
;
GO
-----------------------------------------------------------------------------------------
IF OBJECT_ID('dbo.TallyFunction','IF') IS NOT NULL
DROP FUNCTION dbo.TallyFunction
;
GO
CREATE FUNCTION dbo.TallyFunction
(@Date DATETIME)
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
SELECT [Hour] = DATEPART(HOUR,DATEADD(HOUR,-t.N,@Date))
,[DATE] = DATEADD(HOUR,-t.N,@Date)
FROM dbo.fnTally(1,12) t
;
GO
XÂY DỰNG KIỂM TRA KIỂM TRA ĐỂ KIỂM TRA CHỨC NĂNG
Cuối cùng nhưng không kém phần quan trọng, chúng ta cần một khai thác thử nghiệm. Tôi làm một kiểm tra cơ bản và sau đó kiểm tra từng chức năng theo một cách giống hệt nhau.
Đây là mã cho khai thác thử nghiệm ...
PRINT '--========== Baseline Select =================================';
DECLARE @Hour INT, @Date DATETIME
;
SET STATISTICS TIME,IO ON;
SELECT @Hour = RowNum
,@Date = SomeDateTime
FROM dbo.TestDate
CROSS APPLY dbo.fnTally(1,12);
SET STATISTICS TIME,IO OFF;
GO
PRINT '--========== Orginal Recursive CTE ===========================';
DECLARE @Hour INT, @Date DATETIME
;
SET STATISTICS TIME,IO ON;
SELECT @Hour = fn.[Hour]
,@Date = fn.[Date]
FROM dbo.TestDate td
CROSS APPLY dbo.OriginalrCTE(td.SomeDateTime) fn;
SET STATISTICS TIME,IO OFF;
GO
PRINT '--========== Dedicated Micro-Tally Table =====================';
DECLARE @Hour INT, @Date DATETIME
;
SET STATISTICS TIME,IO ON;
SELECT @Hour = fn.[Hour]
,@Date = fn.[Date]
FROM dbo.TestDate td
CROSS APPLY dbo.MicroTally(td.SomeDateTime) fn;
SET STATISTICS TIME,IO OFF;
GO
PRINT'--========== Physical Tally Table =============================';
DECLARE @Hour INT, @Date DATETIME
;
SET STATISTICS TIME,IO ON;
SELECT @Hour = fn.[Hour]
,@Date = fn.[Date]
FROM dbo.TestDate td
CROSS APPLY dbo.PhysicalTally(td.SomeDateTime) fn;
SET STATISTICS TIME,IO OFF;
GO
PRINT'--========== Tally Function ===================================';
DECLARE @Hour INT, @Date DATETIME
;
SET STATISTICS TIME,IO ON;
SELECT @Hour = fn.[Hour]
,@Date = fn.[Date]
FROM dbo.TestDate td
CROSS APPLY dbo.TallyFunction(td.SomeDateTime) fn;
SET STATISTICS TIME,IO OFF;
GO
Một điều cần chú ý trong khai thác thử nghiệm ở trên là tôi chuyển tất cả đầu ra thành các biến "vứt đi". Đó là cố gắng giữ cho các phép đo hiệu suất càng tinh khiết càng tốt mà không có bất kỳ kết quả xiên đĩa hoặc màn hình nào.
MỘT CÁCH THẬN TRỌNG TRÊN THỐNG KÊ
Ngoài ra, một lời cảnh báo cho những người thử nghiệm sẽ ... Bạn KHÔNG ĐƯỢC sử dụng THỐNG KÊ TẬP HỢP khi kiểm tra các hàm vô hướng hoặc mTVF. Nó chỉ có thể được sử dụng một cách an toàn trên các chức năng iTVF giống như các chức năng trong bài kiểm tra này. THỐNG KÊ SET đã được chứng minh là làm cho các hàm SCALAR chạy chậm hơn hàng trăm lần so với thực tế mà không có nó. Vâng, tôi đang cố gắng nghiêng một cối xay gió khác nhưng đó sẽ là một bài viết dài toàn bộ bài viết và tôi không có thời gian cho việc đó. Tôi có một bài viết trên SQLServerCentral.com nói về điều đó nhưng không có ý nghĩa gì trong việc đăng liên kết ở đây vì ai đó sẽ hiểu rõ về nó.
KẾT QUẢ KIỂM TRA
Vì vậy, đây là kết quả thử nghiệm khi tôi chạy thử nghiệm khai thác trên máy tính xách tay i5 nhỏ của tôi với 6GB RAM.
--========== Baseline Select =================================
Table 'Worktable'. Scan count 1, logical reads 82309, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'TestDate'. Scan count 1, logical reads 105, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 203 ms, elapsed time = 206 ms.
--========== Orginal Recursive CTE ===========================
Table 'Worktable'. Scan count 40001, logical reads 2960000, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'TestDate'. Scan count 1, logical reads 105, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 4258 ms, elapsed time = 4415 ms.
--========== Dedicated Micro-Tally Table =====================
Table 'Worktable'. Scan count 1, logical reads 81989, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'TestDate'. Scan count 1, logical reads 105, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 234 ms, elapsed time = 235 ms.
--========== Physical Tally Table =============================
Table 'Worktable'. Scan count 1, logical reads 81989, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'TestDate'. Scan count 1, logical reads 105, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Tally'. Scan count 1, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 250 ms, elapsed time = 252 ms.
--========== Tally Function ===================================
Table 'Worktable'. Scan count 1, logical reads 81989, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'TestDate'. Scan count 1, logical reads 105, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 250 ms, elapsed time = 253 ms.
"CHỌN BASELINE", chỉ chọn dữ liệu (mỗi hàng được tạo 12 lần để mô phỏng cùng một khối lượng hoàn trả), xuất hiện đúng vào khoảng 1/5 giây. Mọi thứ khác đến vào khoảng một phần tư giây. Vâng, tất cả mọi thứ ngoại trừ chức năng rCTE đẫm máu. Phải mất 4 và 1/4 giây hoặc lâu hơn 16 lần (chậm hơn 1.600%).
Và nhìn vào số lần đọc logic (bộ nhớ IO) ... rCTE đã tiêu thụ một con số khổng lồ 2.960.000 (gần 3 TRIỆU lần đọc) trong khi các chức năng khác chỉ tiêu thụ khoảng 82.100. Điều đó có nghĩa là rCTE tiêu thụ IO nhiều hơn 34,3 lần so với bất kỳ chức năng nào khác.
BỚT TƯ TƯỞNG
Hãy tóm tắt. Phương pháp rCTE để thực hiện điều 12 hàng "nhỏ" này đã sử dụng 16 TIMES (1.600%) CPU (và thời lượng) nhiều hơn và 34.3 TIMES (3.430%) IO bộ nhớ nhiều hơn bất kỳ chức năng nào khác.
Heh ... tôi biết bạn đang nghĩ gì "Thỏa thuận lớn! Nó chỉ là một chức năng."
Vâng, đồng ý, nhưng bạn có bao nhiêu chức năng khác? Bạn có bao nhiêu nơi khác ngoài chức năng? Và bạn có ai trong số những người làm việc với hơn 12 hàng mỗi lần chạy không? Và, có bất kỳ cơ hội nào mà một người nào đó trong một phương thức mua lại có thể sao chép mã rCTE đó cho một cái gì đó lớn hơn nhiều không?
Ok, thời gian để được cùn. Hoàn toàn không có ý nghĩa đối với mọi người để biện minh cho mã bị thách thức hiệu suất chỉ vì số lượng hàng được cho là hạn chế hoặc việc sử dụng. Ngoại trừ khi bạn mua hộp MPP với giá hàng triệu đô la (chưa kể chi phí viết lại mã để hoạt động trên máy đó), bạn không thể mua máy chạy mã nhanh hơn 16 lần (SSD đã thắng Sẽ không làm điều đó ... tất cả những thứ này đều có trong bộ nhớ tốc độ cao khi chúng tôi thử nghiệm nó). Hiệu suất là trong mã. Hiệu suất tốt là trong mã tốt.
Bạn có thể tưởng tượng nếu tất cả các mã của bạn chạy "chỉ" nhanh hơn 16 lần?
Không bao giờ biện minh cho mã xấu hoặc hiệu năng bị thách thức trên số lượng hàng thấp hoặc thậm chí sử dụng thấp. Nếu bạn làm thế, bạn có thể phải mượn một trong những cối xay gió mà tôi bị cáo buộc nghiêng để giữ cho CPU và đĩa của bạn đủ mát. ;-)
MỘT CÔNG VIỆC TRÊN CÔNG VIỆC "TALLY"
Vâng tôi đồng ý. Về mặt ngữ nghĩa, Bảng Tally chứa các số, không phải là "số đo". Trong bài viết gốc của tôi về chủ đề này (nó không phải là bài viết gốc về kỹ thuật nhưng nó là bài viết đầu tiên của tôi về nó), tôi gọi nó là "Tally" không phải vì những gì nó chứa, mà vì những gì nó ... được sử dụng để "đếm" thay vì lặp và "Tally" một cái gì đó là "Đếm" thứ gì đó. ;-) Gọi nó là những gì bạn sẽ ... Bảng số, Bảng kiểm đếm, Bảng tuần tự, bất cứ điều gì. Tôi không quan tâm. Đối với tôi, "Tally" có nghĩa đầy đủ hơn và, là một DBA lười biếng tốt, chỉ chứa 5 chữ cái (2 giống hệt nhau) thay vì 7 và dễ nói hơn với hầu hết mọi người. Nó cũng là "số ít", theo quy ước đặt tên của tôi cho các bảng. ;-) Nó ' Đây cũng là những gì bài báo có chứa một trang từ một cuốn sách từ những năm 60 được gọi là nó. Tôi sẽ luôn gọi nó là "Bảng kiểm đếm" và bạn vẫn sẽ biết ý nghĩa của tôi hoặc người khác. Tôi cũng tránh ký hiệu Hungary như bệnh dịch hạch nhưng gọi hàm này là "fnTally" để tôi có thể nói "Chà, nếu bạn sử dụng Hàm Tally eff-en tôi đã chỉ cho bạn, bạn sẽ không gặp vấn đề về hiệu năng" mà không thực sự là vấn đề Vi phạm nhân sự. ;-) mà không thực sự là một vi phạm nhân sự. ;-) mà không thực sự là một vi phạm nhân sự. ;-)
Điều tôi quan tâm hơn là mọi người học cách sử dụng nó đúng cách thay vì dùng đến những thứ như hiệu suất thách thức các rCTE và các hình thức khác của RBAR ẩn.