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

Giải pháp thử thách trình tạo chuỗi số - Phần 5

Đây là phần thứ năm và là phần cuối cùng trong loạt bài bao gồm các giải pháp cho thử thách máy phát chuỗi số. Trong Phần 1, Phần 2, Phần 3 và Phần 4, tôi đã đề cập đến các giải pháp T-SQL thuần túy. Ngay từ đầu khi tôi đăng câu đố, một số người đã nhận xét rằng giải pháp hoạt động tốt nhất có thể sẽ là giải pháp dựa trên CLR. Trong bài viết này, chúng tôi sẽ đưa giả định trực quan này vào bài kiểm tra. Cụ thể, tôi sẽ đề cập đến các giải pháp dựa trên CLR được đăng bởi Kamil Kosno và Adam Machanic.

Rất cảm ơn Alan Burstein, Joe Obbish, Adam Machanic, Christopher Ford, Jeff Moden, Charlie, NoamGr, Kamil Kosno, Dave Mason, John Nelson # 2, Ed Wagner, Michael Burbea và Paul White đã chia sẻ ý kiến ​​và nhận xét của bạn.

Tôi sẽ thực hiện kiểm tra của mình trong cơ sở dữ liệu có tên là testdb. Sử dụng mã sau để tạo cơ sở dữ liệu nếu nó không tồn tại và để bật I / O và thống kê thời gian:

 - BẬT DB và số liệu thống kê; ĐẶT THỐNG KÊ IO, BẬT THỜI GIAN; ĐI NẾU DB_ID ('testdb') KHÔNG TẠO CƠ SỞ DỮ LIỆU testdb; ĐI SỬ DỤNG testdb; ĐI 

Vì lợi ích của đơn giản, tôi sẽ tắt tính năng bảo mật nghiêm ngặt CLR và làm cho cơ sở dữ liệu đáng tin cậy bằng cách sử dụng mã sau:

 - Bật CLR, tắt bảo mật nghiêm ngặt CLR và đặt db đáng tin cậy .EXEC sys.sp_configure 'hiển thị cài đặt nâng cao', 1; RECONFIGURE; EXEC sys.sp_configure 'clr đã được kích hoạt', 1; EXEC sys.sp_configure 'clr nghiêm ngặt bảo mật', 0; RECONFIGURE; EXEC sys.sp_configure 'hiển thị cài đặt nâng cao', 0; RECONFIGURE; ALTER DATABASE testdb THIẾT LẬP TIN CẬY BẬT; ĐI 

Các giải pháp trước đó

Trước khi tôi đề cập đến các giải pháp dựa trên CLR, hãy nhanh chóng xem xét hiệu suất của hai trong số các giải pháp T-SQL hoạt động tốt nhất.

Giải pháp T-SQL hoạt động tốt nhất không sử dụng bất kỳ bảng cơ sở cố định nào (ngoài bảng cột trụ trống giả để xử lý hàng loạt) và do đó không liên quan đến hoạt động I / O, là giải pháp được triển khai trong hàm dbo.GetNumsAlanCharlieItzikBatch. Tôi đã đề cập đến giải pháp này trong Phần 1.

Đây là mã để tạo bảng cột lưu trữ trống giả mà truy vấn của hàm sử dụng:

 DROP TABLE NẾU TỒN TẠI dbo.BatchMe; ĐI TẠO BẢNG dbo.BatchMe (col1 INT NOT NULL, INDEX idx_cs CLUSTERED COLUMNSTORE); ĐI 

Và đây là mã với định nghĩa của hàm:

 TẠO HOẶC ALTER CHỨC NĂNG dbo.GetNumsAlanCharlieItzikBatch (@low AS BIGINT =1, @high AS BIGINT) BẢNG TRẢ LẠI CÓ L0 AS (CHỌN 1 AS c FROM (VALUES (1), (1), (1), (1 ), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) AS D (c)), L1 AS (CHỌN 1 AS c TỪ L0 AS A CROSS JOIN L0 AS B), L2 AS (CHỌN 1 AS c TỪ L1 AS A CROSS JOIN L1 AS B), L3 AS (CHỌN 1 AS c TỪ L2 NHƯ CHÉO THAM GIA L2 NHƯ B), Nums NHƯ (CHỌN ROW_NUMBER () HẾT (ĐẶT HÀNG BẰNG (CHỌN NULL)) NHƯ rownum TỪ L3) CHỌN ĐẦU (@high - @low + 1) rownum AS rn, @high + 1 - rownum AS op, @low - 1 + rownum AS n TỪ Nums TRÁI RA NGOÀI THAM GIA dbo.BatchMe ON 1 =0 ORDER BY rownum; GO 

Đầu tiên chúng ta hãy kiểm tra hàm yêu cầu một chuỗi 100 triệu số, với tổng số MAX được áp dụng cho cột n:

 CHỌN TỐI ĐA (n) NHƯ mx TỪ dbo.GetNumsAlanCharlieItzikBatch (1, 100000000) TÙY CHỌN (MAXDOP 1); 

Nhắc lại, kỹ thuật kiểm tra này tránh truyền 100 triệu hàng tới trình gọi và cũng tránh nỗ lực ở chế độ hàng liên quan đến việc gán biến khi sử dụng kỹ thuật gán biến.

Dưới đây là số liệu thống kê về thời gian mà tôi nhận được cho bài kiểm tra này trên máy của mình:

Thời gian CPU =6719 mili giây, thời gian đã trôi qua = 6742 mili giây .

Tất nhiên, việc thực thi chức năng này không tạo ra bất kỳ lần đọc logic nào.

Tiếp theo, hãy kiểm tra nó theo thứ tự, sử dụng kỹ thuật gán biến số:

 DECLARE @n AS BIGINT; CHỌN @n =n TỪ dbo.GetNumsAlanCharlieItzikBatch (1, 100000000) ĐẶT HÀNG THEO n TÙY CHỌN (MAXDOP 1); 

Tôi nhận được số liệu thống kê về thời gian sau cho quá trình thực thi này:

Thời gian CPU =9468 ms, thời gian đã trôi qua = 9531 ms .

Nhớ lại rằng hàm này không dẫn đến việc sắp xếp khi yêu cầu dữ liệu được sắp xếp theo thứ tự của n; về cơ bản bạn sẽ nhận được cùng một gói cho dù bạn có yêu cầu dữ liệu theo thứ tự hay không. Chúng tôi có thể quy phần lớn thời gian thừa trong thử nghiệm này so với thử nghiệm trước đó cho 100 triệu bài tập biến dựa trên chế độ hàng.

Giải pháp T-SQL hoạt động tốt nhất đã sử dụng bảng cơ sở liên tục và do đó dẫn đến một số hoạt động I / O, mặc dù rất ít, là giải pháp của Paul White được triển khai trong hàm dbo.GetNums_SQLkiwi. Tôi đã đề cập đến giải pháp này trong Phần 4.

Đây là mã của Paul để tạo cả bảng cột lưu trữ được sử dụng bởi hàm và chính hàm:

 - Bảng lưu trữ cột trợ giúp BẢNG XÓA NẾU TỒN TẠI dbo.CS; - 64K hàng (đủ cho 4B hàng khi nối chéo) - cột 1 luôn bằng 0 - cột 2 là (1 ... 65536) CHỌN - nhập dưới dạng số nguyên NOT NULL - (mọi thứ được chuẩn hóa thành 64 bit trong columnstore / vẫn giữ chế độ hàng loạt) n1 =ISNULL (CONVERT (số nguyên, 0), 0), n2 =ISNULL (CONVERT (số nguyên, N.rn), 0) INTO dbo.CSFROM (CHỌN rn =ROW_NUMBER () HẾT (ĐẶT HÀNG BẰNG @@ SPID) TỪ master.dbo.spt_values ​​AS SV1 CROSS JOIN master.dbo.spt_values ​​AS SV2 ORDER BY rn ASC OFFSET 0 ROWS FETCH NEXT 65536 ROWS ONLY) AS N; - Nhóm hàng nén duy nhất gồm 65.536 hàng .rn, n =@low - 1 + N.rn, op =@high + 1 - N.rn FROM (CHỌN - Sử dụng @@ TRANCOUNT thay vì @@ SPID nếu bạn muốn tất cả các truy vấn của mình nối tiếp rn =ROW_NUMBER () HẾT (ĐẶT HÀNG BẰNG @@ SPID ASC) TỪ dbo.CS AS N1 THAM GIA dbo.CS AS N2 - Tham gia chéo chế độ Batch - Kiểu dữ liệu số nguyên không null tránh dư đầu dò băm - Đây luôn là 0 =0 ON N2. n1 =N1.n1 WHERE - Cố gắng tránh SQRT trên các số âm và bật tính năng đơn giản hóa - để quét liên tục duy nhất nếu @low> @high (với chữ) - Không có bộ lọc khởi động ở chế độ hàng loạt @high> =@low - Bộ lọc thô:- Hạn chế mỗi bên của phép nối chéo với SQRT (số hàng mục tiêu) - IIF tránh SQRT trên các số âm có tham số VÀ N1.n2 <=CONVERT (số nguyên, CEILING (SQRT (CONVERT (float, IIF (@high> =@low, @high) - @low + 1, 0))))) VÀ N2.n2 <=CONVERT (số nguyên, TRẦN (SQRT (CHUYỂN ĐỔI (float, IIF (@high> =@low, @high - @low + 1, 0))) )))) AS N WHERE - Bộ lọc chính xác:- Chế độ hàng loạt lọc kết nối chéo giới hạn đến số hàng chính xác cần thiết - Tránh trình tối ưu hóa giới thiệu chế độ hàng Trên cùng với chế độ hàng sau tính toán vô hướng @low - 2 + N.rn <@high; ĐI 

Đầu tiên chúng ta hãy kiểm tra nó mà không cần đặt hàng bằng kỹ thuật tổng hợp, dẫn đến kế hoạch chế độ tất cả các đợt:

 CHỌN TỐI ĐA (n) NHƯ mx TỪ dbo.GetNums_SQLkiwi (1, 100000000) TÙY CHỌN (MAXDOP 1); 

Tôi nhận được thời gian và số liệu thống kê I / O sau đây cho việc thực thi này:

Thời gian CPU =2922 ms, thời gian đã trôi qua = 2943 ms .

Bảng 'CS'. Quét đếm 2, đọc logic 0, đọc vật lý 0, máy chủ trang đọc 0, đọc trước đọc 0, đọc trước máy chủ trang đọc 0, lob logic đọc 44 , máy chủ trang lob đọc 0, máy chủ trang lob đọc 0, máy chủ trang lob đọc trước 0, máy chủ trang lob đọc trước đọc 0.

Bảng 'CS'. Phân đoạn đọc 2, phân đoạn bị bỏ qua 0.

Hãy kiểm tra hàm theo thứ tự bằng cách sử dụng kỹ thuật gán biến số:

 DECLARE @n AS BIGINT; CHỌN @n =n TỪ dbo.GetNums_SQLkiwi (1, 100000000) ĐƠN HÀNG THEO n TÙY CHỌN (MAXDOP 1); 

Giống như giải pháp trước đó, giải pháp này cũng tránh sắp xếp rõ ràng trong kế hoạch và do đó sẽ nhận được cùng một kế hoạch cho dù bạn có yêu cầu dữ liệu được sắp xếp hay không. Nhưng một lần nữa, thử nghiệm này phải chịu thêm một hình phạt chủ yếu do kỹ thuật gán biến được sử dụng ở đây, dẫn đến phần gán biến trong kế hoạch được xử lý ở chế độ hàng.

Đây là thời gian và số liệu thống kê I / O mà tôi nhận được cho việc thực hiện này:

Thời gian CPU =6985 ms, thời gian đã trôi qua = 7033 ms .

Bảng 'CS'. Quét đếm 2, đọc logic 0, đọc vật lý 0, máy chủ trang đọc 0, đọc trước đọc 0, đọc trước máy chủ trang đọc 0, lob logic đọc 44 , máy chủ trang lob đọc 0, máy chủ trang lob đọc 0, máy chủ trang lob đọc trước 0, máy chủ trang lob đọc trước đọc 0.

Bảng 'CS'. Phân đoạn đọc 2, phân đoạn bị bỏ qua 0.

Giải pháp CLR

Cả Kamil Kosno và Adam Machanic lần đầu tiên cung cấp một giải pháp đơn giản chỉ dành cho CLR, và sau đó đã đưa ra một kết hợp CLR + T-SQL phức tạp hơn. Tôi sẽ bắt đầu với các giải pháp của Kamil và sau đó đề cập đến các giải pháp của Adam.

Giải pháp của Kamil Kosno

Đây là mã CLR được sử dụng trong giải pháp đầu tiên của Kamil để xác định một hàm có tên GetNums_KamilKosno1:

 using System; using System.Data.SqlTypes; using System.Collections; public từng phần lớp GetNumsKamil1 {[Microsoft.SqlServer.Server.SqlFunction (FillRowMethodName ="GetNums_KamilKosno1_Fill", TableDefinition ="n BIGINT")] public static IEnumerator (SqlInt64 thấp, SqlInt64 cao) {return (low.IsNull || high.IsNull)? new GetNumsCS (0, 0):GetNumsCS mới (low.Value, high.Value); } public static void GetNums_KamilKosno1_Fill (Object o, out SqlInt64 n) {n =(long) o; } private class GetNumsCS:IEnumerator {public GetNumsCS (long from, long to) {_lowrange =from; _current =_lowrange - 1; _highrange =đến; } public bool MoveNext () {_current + =1; if (_current> _highrange) trả về false; khác trả về true; } đối tượng public Hiện tại {get {return _current; }} public void Reset () {_current =_lowrange - 1; } long _lowrange; _ dài dòng; dài _highrange; }} 

Hàm chấp nhận hai đầu vào được gọi là thấp và cao và trả về một bảng có cột BIGINT được gọi là n. Hàm là một loại truyền trực tuyến, trả về một hàng với số tiếp theo trong chuỗi trên mỗi hàng yêu cầu từ truy vấn gọi. Như bạn có thể thấy, Kamil đã chọn phương pháp chính thức hóa hơn để triển khai giao diện IEnumerator, liên quan đến việc triển khai các phương thức MoveNext (nâng cao trình điều tra để có hàng tiếp theo), Hiện tại (lấy hàng ở vị trí điều tra viên hiện tại) và Đặt lại (bộ điều tra viên đến vị trí ban đầu của nó, trước hàng đầu tiên).

Biến giữ số hiện tại trong chuỗi được gọi là _current. Hàm tạo đặt _current thành giới hạn thấp của phạm vi được yêu cầu trừ đi 1 và tương tự với phương thức Đặt lại. Phương thức MoveNext tăng _current lên 1. Sau đó, nếu _current lớn hơn giới hạn cao của phạm vi được yêu cầu, phương thức trả về false, nghĩa là nó sẽ không được gọi lại. Nếu không, nó trả về true, nghĩa là nó sẽ được gọi lại. Phương thức Hiện tại trả về _current một cách tự nhiên. Như bạn có thể thấy, logic khá cơ bản.

Tôi đã gọi dự án Visual Studio là GetNumsKamil1 và sử dụng đường dẫn C:\ Temp \ cho nó. Đây là mã tôi đã sử dụng để triển khai hàm trong cơ sở dữ liệu testdb:

 CHỨC NĂNG DROP NẾU TỒN TẠI dbo.GetNums_KamilKosno1; DROP LẮP RÁP NẾU TỒN TẠI GetNumsKamil1; HÃY TẠO LẮP RÁP GetNumsKamil1 TỪ 'C:\ Temp \ GetNumsKamil1 \ GetNumsKamil1 \ bin \ Debug \ GetNumsKamil1.dll'; ĐI TẠO CHỨC NĂNG dbo.GetNET_KamilKosno1 =1 Thấp, NHƯ @ LỚN BẢNG (n LỚN) LỆNH (n) NHƯ TÊN BÊN NGOÀI GetNumsKamil1.GetNumsKamil1.GetNums_KamilKosno1; ĐI 

Lưu ý việc sử dụng mệnh đề ORDER trong câu lệnh CREATE FUNCTION. Hàm phát ra các hàng theo thứ tự n, vì vậy khi các hàng cần được nhập vào kế hoạch theo thứ tự n, dựa trên mệnh đề này SQL Server biết rằng nó có thể tránh sắp xếp trong kế hoạch.

Trước tiên, hãy kiểm tra chức năng bằng kỹ thuật tổng hợp, khi không cần đặt hàng:

 CHỌN TỐI ĐA (n) NHƯ mx TỪ dbo.GetNums_KamilKosno1 (1, 100000000); 

Tôi nhận được kế hoạch được hiển thị trong Hình 1.

Hình 1:Kế hoạch cho hàm dbo.GetNums_KamilKosno1

Không có nhiều điều để nói về kế hoạch này, ngoài thực tế là tất cả các toán tử đều sử dụng chế độ thực thi hàng.

Tôi nhận được số liệu thống kê về thời gian sau cho quá trình thực thi này:

Thời gian CPU =37375 mili giây, thời gian đã trôi qua = 37488 mili giây .

Và tất nhiên, không có lần đọc logic nào được tham gia.

Hãy kiểm tra hàm theo thứ tự, sử dụng kỹ thuật gán biến số:

 DECLARE @n AS BIGINT; CHỌN @n =n TỪ dbo.GetNums_KamilKosno1 (1, 100000000) ĐƠN HÀNG THEO n; 

Tôi nhận được kế hoạch được hiển thị trong Hình 2 cho việc thực thi này.

Hình 2:Kế hoạch cho hàm dbo.GetNums_KamilKosno1 với ORDER BY

Lưu ý rằng không có sắp xếp trong kế hoạch vì hàm được tạo bằng mệnh đề ORDER (n). Tuy nhiên, có một số nỗ lực trong việc đảm bảo rằng các hàng thực sự được phát ra từ hàm theo thứ tự đã hứa. Điều này được thực hiện bằng cách sử dụng toán tử Dự án phân đoạn và trình tự, được sử dụng để tính toán số hàng và toán tử Assert, sẽ hủy bỏ việc thực thi truy vấn nếu thử nghiệm không thành công. Công việc này có chia tỷ lệ tuyến tính — không giống như chia tỷ lệ n log n mà bạn sẽ nhận được yêu cầu sắp xếp — nhưng nó vẫn không rẻ. Tôi nhận được số liệu thống kê về thời gian sau cho bài kiểm tra này:

Thời gian CPU =51531 ms, thời gian đã trôi qua = 51905 ms .

Kết quả có thể gây ngạc nhiên cho một số người — đặc biệt là những người cho rằng các giải pháp dựa trên CLR sẽ hoạt động tốt hơn các giải pháp T-SQL. Như bạn có thể thấy, thời gian thực thi dài hơn một bậc so với giải pháp T-SQL hoạt động tốt nhất của chúng tôi.

Giải pháp thứ hai của Kamil là kết hợp CLR-T-SQL. Ngoài đầu vào thấp và cao, hàm CLR (GetNums_KamilKosno2) thêm đầu vào bước và trả về các giá trị giữa thấp và cao cách xa nhau. Đây là mã CLR mà Kamil đã sử dụng trong giải pháp thứ hai của mình:

 using System; using System.Data.SqlTypes; using System.Collections; public một phần lớp GetNumsKamil2 {[Microsoft.SqlServer.Server.SqlFunction (DataAccess =Microsoft.SqlServer.Server.DataAccessKind.None, IsDeterministic =true, IsPrecise =true, FillRowMethodName ="GetNums_FINTill", TableDefinition ")] n B staticIG IEnumerator GetNums_KamilKosno2 (SqlInt64 thấp, SqlInt64 cao, SqlInt64 bước) {return (low.IsNull || high.IsNull)? new GetNumsCS (0, 0, step.Value):GetNumsCS mới (low.Value, high.Value, step.Value); } public static void GetNums_Fill (Object o, out SqlInt64 n) {n =(long) o; } private class GetNumsCS:IEnumerator {public GetNumsCS (long from, long to, long step) {_lowrange =from; _step =bước; _current =_lowrange - _step; _highrange =đến; } public bool MoveNext () {_current =_current + _step; if (_current> _highrange) trả về false; khác trả về true; } đối tượng public Hiện tại {get {return _current; }} public void Reset () {_current =_lowrange - _step; } long _lowrange; _ dài dòng; dài _highrange; _bước dài; }} 

Tôi đặt tên cho dự án VS là GetNumsKamil2, đặt nó trong đường dẫn C:\ Temp \, và sử dụng mã sau để triển khai nó trong cơ sở dữ liệu testdb:

 - Tạo lắp ráp và chức năng DROP FUNCTION NẾU TỒN TẠI dbo.GetNums_KamilKosno2; DROP LẮP NẾU TỒN TẠI GetNumsKamil2; ĐI TẠO LẮP RÁP GetNumsKamil2 TỪ 'C:\ Temp \ GetNumsKamil2 \ GetNumsKamil2 \ bin \ Debug2.dllumsKamil2 \ bin \ Debug2.dllums' .GetNums_KamilKosno2 (@low AS BIGINT =1, @high AS BIGINT, @step AS BIGINT) BẢNG TRẢ LẠI (n BIGINT) LỆNH (n) NHƯ TÊN BÊN NGOÀI GetNumsKamil2.GetNumsKamil2.GetNums_KamilKosno2; ĐI 

Để làm ví dụ cho việc sử dụng hàm, đây là yêu cầu tạo các giá trị từ 5 đến 59, với bước 10:

 CHỌN n TỪ dbo.GetNums_KamilKosno2 (5, 59, 10); 

Mã này tạo ra kết quả sau:

 n --- 51525354555 

Đối với phần T-SQL, Kamil đã sử dụng một hàm có tên là dbo.GetNums_Hybrid_Kamil2, với đoạn mã sau:

 TẠO HOẶC ALTER CHỨC NĂNG dbo.GetNums_Hybrid_Kamil2 (@low AS BIGINT, @high AS BIGINT) BẢNG QUAY LẠI LỰA CHỌN ĐẦU (@high - @low + 1) V.n FROM dbo.GetNums_KamilKosno2 (@low, @high, 10) AS GN ÁP DỤNG CROSS (GIÁ TRỊ (0 + GN.n), (1 + GN.n), (2 + GN.n), (3 + GN.n), (4 + GN.n), (5 + GN.n ), (6 + GN.n), (7 + GN.n), (8 + GN.n), (9 + GN.n)) AS V (n); ĐI 

Như bạn có thể thấy, hàm T-SQL gọi hàm CLR với cùng đầu vào @low và @high mà nó nhận được và trong ví dụ này sử dụng kích thước bước là 10. Truy vấn sử dụng ÁP DỤNG CHÉO giữa kết quả của hàm CLR và một phương thức tạo bảng-giá trị tạo ra các số cuối cùng bằng cách thêm các giá trị trong phạm vi từ 0 đến 9 vào đầu bước. Bộ lọc TOP được sử dụng để đảm bảo rằng bạn không nhận được nhiều hơn số lượng mà bạn đã yêu cầu.

Quan trọng: Tôi nên nhấn mạnh rằng Kamil đưa ra giả định ở đây về việc bộ lọc TOP được áp dụng dựa trên thứ tự số kết quả, điều này không thực sự được đảm bảo vì truy vấn không có mệnh đề ORDER BY. Nếu bạn thêm mệnh đề ORDER BY để hỗ trợ TOP hoặc thay thế TOP bằng bộ lọc WHERE, để đảm bảo bộ lọc xác định, điều này có thể thay đổi hoàn toàn cấu hình hiệu suất của giải pháp.

Dù sao đi nữa, trước tiên chúng ta hãy kiểm tra chức năng mà không có thứ tự bằng cách sử dụng kỹ thuật tổng hợp:

 CHỌN TỐI ĐA (n) NHƯ mx TỪ dbo.GetNums_Hybrid_Kamil2 (1, 100000000); 

Tôi nhận được kế hoạch được hiển thị trong Hình 3 cho việc thực thi này.

Hình 3:Kế hoạch cho hàm dbo.GetNums_Hybrid_Kamil2

Một lần nữa, tất cả các toán tử trong kế hoạch sử dụng chế độ thực thi hàng.

Tôi nhận được số liệu thống kê về thời gian sau cho quá trình thực thi này:

Thời gian CPU =13985 ms, thời gian đã trôi qua = 14069 ms .

Và tự nhiên không có lần đọc logic nào.

Hãy kiểm tra chức năng với thứ tự:

 DECLARE @n AS BIGINT; CHỌN @n =n TỪ dbo.GetNums_Hybrid_Kamil2 (1, 100000000) ĐƠN ĐẶT HÀNG THEO n; 

Tôi nhận được kế hoạch được hiển thị trong Hình 4.

Hình 4:Kế hoạch cho hàm dbo.GetNums_Hybrid_Kamil2 với ORDER BY

Vì các số kết quả là kết quả của việc thao tác giới hạn thấp của bước được trả về bởi hàm CLR và delta được thêm vào hàm tạo giá trị bảng, trình tối ưu hóa không tin tưởng rằng các số kết quả được tạo theo thứ tự được yêu cầu và thêm phân loại rõ ràng vào kế hoạch.

Tôi nhận được số liệu thống kê về thời gian sau cho quá trình thực thi này:

Thời gian CPU =68703 ms, thời gian đã trôi qua = 84538 ms .

Vì vậy, có vẻ như khi không cần đặt hàng, giải pháp thứ hai của Kamil hoạt động tốt hơn giải pháp đầu tiên của anh ấy. Nhưng khi cần đặt hàng, thì ngược lại. Dù bằng cách nào, các giải pháp T-SQL đều nhanh hơn. Cá nhân tôi tin tưởng vào tính đúng đắn của giải pháp đầu tiên, nhưng không phải giải pháp thứ hai.

Giải pháp của Adam Machanic

Giải pháp đầu tiên của Adam cũng là một hàm CLR cơ bản giúp tăng bộ đếm. Chỉ thay vì sử dụng cách tiếp cận chính thức hóa có liên quan hơn như Kamil đã làm, Adam đã sử dụng một cách tiếp cận đơn giản hơn gọi lệnh năng suất trên mỗi hàng cần được trả lại.

Đây là mã CLR của Adam cho giải pháp đầu tiên của anh ấy, xác định chức năng phát trực tuyến có tên GetNums_AdamMachanic1:

 using System.Data.SqlTypes; using System.Collections; public một phần lớp GetNumsAdam1 {[Microsoft.SqlServer.Server.SqlFunction (FillRowMethodName ="GetNums_AdamMachanic1_fill", TableDefinition ="n BIGINT")] công khai tĩnh IEnumerable GetNums_AdamMachanic1 (max.VIntal64 minint, Sql, max. var max_int =max.Value; for (; min_int <=max_int; min_int ++) {lợi nhuận trả về (min_int); }} public static void GetNums_AdamMachanic1_fill (object o, out long i) {i =(long) o; }}; 

Giải pháp rất thanh lịch trong sự đơn giản của nó. Như bạn có thể thấy, hàm chấp nhận hai đầu vào được gọi là min và max đại diện cho các điểm biên thấp và cao của phạm vi được yêu cầu và trả về một bảng có cột BIGINT được gọi là n. Hàm khởi tạo các biến được gọi là min_int và max_int với các giá trị tham số đầu vào của hàm tương ứng. Sau đó, hàm sẽ chạy một vòng lặp với điều kiện là min_int <=max_int, trong mỗi lần lặp sẽ tạo ra một hàng có giá trị hiện tại là min_int và tăng min_int lên 1. Vậy là xong.

Tôi đặt tên dự án là GetNumsAdam1 trong VS, đặt nó trong C:\ Temp \ và sử dụng mã sau để triển khai nó:

 - Tạo assembly và functionDROP FUNCTION NẾU TỒN TẠI dbo.GetNums_AdamMachanic1; DROP ASSEMBLY NẾU TỒN TẠI GetNumsAdam1; ĐI TẠO LẮP RÁP GetNumsAdam1 TỪ 'C:\ Temp \ GetNumsAdam1 \ GetNumsAdam1 \ bin \ Debug1 \ GetNumsAdam1 \ bin \ Debug1. .GetNums_AdamMachanic1 (@low AS BIGINT =1, @high AS BIGINT) BẢNG TRẢ LẠI (n BIGINT) ĐẶT HÀNG (n) NHƯ TÊN BÊN NGOÀI GetNumsAdam1.GetNumsAdam1.GetNums_AdamMachanic1; ĐI 

Tôi đã sử dụng mã sau để kiểm tra nó bằng kỹ thuật tổng hợp, cho các trường hợp khi đơn đặt hàng không quan trọng:

 CHỌN TỐI ĐA (n) NHƯ mx TỪ dbo.GetNums_AdamMachanic1 (1, 100000000); 

Tôi nhận được kế hoạch được hiển thị trong Hình 5 cho việc thực thi này.

Hình 5:Kế hoạch cho hàm dbo.GetNums_AdamMachanic1

Kế hoạch này rất giống với kế hoạch bạn đã thấy trước đó cho giải pháp đầu tiên của Kamil và điều tương tự cũng áp dụng cho hiệu suất của nó. Tôi nhận được số liệu thống kê về thời gian sau cho quá trình thực thi này:

Thời gian CPU =36687 ms, thời gian đã trôi qua = 36952 ms .

Và tất nhiên không cần đọc logic.

Hãy kiểm tra hàm theo thứ tự, sử dụng kỹ thuật gán biến số:

 DECLARE @n AS BIGINT; CHỌN @n =n TỪ dbo.GetNums_AdamMachanic1 (1, 100000000) ĐẶT HÀNG THEO n; 

Tôi nhận được kế hoạch được hiển thị trong Hình 6 cho việc thực thi này.

Hình 6:Kế hoạch cho hàm dbo.GetNums_AdamMachanic1 với ORDER BY

Một lần nữa, kế hoạch trông giống với kế hoạch bạn đã thấy trước đó cho giải pháp đầu tiên của Kamil. Không cần sắp xếp rõ ràng vì hàm được tạo bằng mệnh đề ORDER, nhưng kế hoạch này bao gồm một số công việc để xác minh rằng các hàng thực sự được trả về theo thứ tự như đã hứa.

Tôi nhận được số liệu thống kê về thời gian sau cho quá trình thực thi này:

Thời gian CPU =55047 ms, thời gian đã trôi qua = 55498 ms .

Trong giải pháp thứ hai của mình, Adam cũng kết hợp một phần CLR và một phần T-SQL. Dưới đây là mô tả của Adam về logic mà anh ấy đã sử dụng trong giải pháp của mình:

“Tôi đang cố gắng nghĩ cách giải quyết vấn đề trò chuyện SQLCLR, và cũng là thách thức chính của trình tạo số này trong T-SQL, đó là thực tế là chúng ta không thể đơn giản hóa các hàng tồn tại.

CLR là một câu trả lời tốt cho phần thứ hai nhưng tất nhiên bị cản trở bởi vấn đề đầu tiên. Vì vậy, như một sự thỏa hiệp, tôi đã tạo một T-SQL TVF [được gọi là GetNums_AdamMachanic2_8192] được mã hóa cứng với các giá trị từ 1 đến 8192. (Lựa chọn khá tùy ý, nhưng quá lớn và QO bắt đầu nghẹt thở với nó một chút.) Tiếp theo, tôi sửa đổi hàm CLR của mình [ được đặt tên là GetNums_AdamMachanic2_8192_base] để xuất ra hai cột, "max_base" và "base_add", và nó xuất ra các hàng như:

    max_base, base_add
    ——————
    8191, 1
    8192, 8192
    8192, 16384

    8192, 99991552
    257, 99999744

Bây giờ nó là một vòng lặp đơn giản. Đầu ra CLR được gửi đến T-SQL TVF, được thiết lập để chỉ trả về tối đa các hàng "max_base" của tập hợp mã cứng của nó. Và đối với mỗi hàng, nó thêm "base_add" vào giá trị, do đó tạo ra các số cần thiết. Chìa khóa ở đây, tôi nghĩ, là chúng ta có thể tạo N hàng chỉ với một phép nối chéo logic duy nhất và hàm CLR chỉ phải trả về 1/8192 bao nhiêu hàng, vì vậy nó đủ nhanh để hoạt động như bộ tạo cơ sở. ”

Logic có vẻ khá đơn giản.

Đây là mã được sử dụng để xác định hàm CLR có tên GetNums_AdamMachanic2_8192_base:

 using System.Data.SqlTypes; using System.Collections; public từng phần lớp GetNumsAdam2 {private struct row {public long max_base; public long base_add; } [Microsoft.SqlServer.Server.SqlFunction (FillRowMethodName ="GetNums_AdamMachanic2_8192_base_fill", TableDefinition ="max_base int, base_add int")] công khai tĩnh IEnumerable GetNums_AdamMachanic2_8192.lvalbase minnt64 minint64 minint64 minint64 minint64 var max_int =max.Value; var min_group =min_int / 8192; var max_group =max_int / 8192; for (; min_group <=max_group; min_group ++) {if (min_int> max_int) phá vỡ sản lượng; var max_base =8192 - (min_int% 8192); if (min_group ==max_group &&max_int <(((max_int / 8192) + 1) * 8192) - 1) max_base =max_int - min_int + 1; lợi nhuận trả về (hàng mới () {max_base =max_base, base_add =min_int}); min_int =(min_group + 1) * 8192; }} public static void GetNums_AdamMachanic2_8192_base_fill (object o, out long max_base, out long base_add) {var r =(row) o; max_base =r.max_base; base_add =r.base_add; }}; 

Tôi đặt tên dự án VS là GetNumsAdam2 và đặt trong đường dẫn C:\ Temp \ như với các dự án khác. Đây là mã tôi đã sử dụng để triển khai hàm trong cơ sở dữ liệu testdb:

 - Tạo hợp ngữ và hàm DROP FUNCTION NẾU TỒN TẠI dbo.GetNums_AdamMachanic2_8192_base; DROP ASSEMBLY NẾU TỒN TẠI GetNumsAdam2; ĐI TẠO LẮP RÁP GetNumsAdam2 TỪ 'C:\ Temp \ GetNumsAdam2 \ GetNumsAdam2 .GetNums_AdamMachanic2_8192_base (@max_base AS BIGINT, @add_base AS BIGINT) BẢNG RETURNS (max_base BIGINT, base_add BIGINT) LỆNH (base_add) LÀ TÊN BÊN NGOÀI GetNums_Adam2.GetNumsAdam2.GetNums_Adam2.GetNumsAdam2.GetNums_Adam 

Dưới đây là ví dụ về việc sử dụng GetNums_AdamMachanic2_8192_base với phạm vi từ 1 đến 100 triệu:

 CHỌN * TỪ dbo.GetNums_AdamMachanic2_8192_base (1, 100000000); 

Mã này tạo ra kết quả sau, được hiển thị ở đây dưới dạng viết tắt:

 max_base base_add -------------------------------- 8191 18192 81928192 163848192 245768192 32768 ... 8192 999669768192 999751688192 999833608192 99991552257 99999744 (12208 hàng bị ảnh hưởng) 

Đây là mã với định nghĩa của hàm T-SQL GetNums_AdamMachanic2_8192 (viết tắt):

 TẠO HOẶC CHỨC NĂNG ALTER dbo.GetNums_AdamMachanic2_8192 (@max_base AS BIGINT, @add_base AS BIGINT) BẢNG TRỞ LẠIASRETURN CHỌN ĐẦU (@max_base) V.i + @add_base AS val FROM (VALUES (0), (1), (2), (3), (4), ... (8187), (8188), (8189), (8190), (8191)) NHƯ V (i); ĐI 

Quan trọng: Cũng ở đây, tôi nên nhấn mạnh rằng tương tự như những gì tôi đã nói về giải pháp thứ hai của Kamil, ở đây Adam đưa ra giả định rằng bộ lọc TOP sẽ trích xuất các hàng trên cùng dựa trên thứ tự xuất hiện của hàng trong hàm tạo giá trị bảng, điều này không thực sự được đảm bảo. Nếu bạn thêm mệnh đề ORDER BY để hỗ trợ TOP hoặc thay đổi bộ lọc thành bộ lọc WHERE, bạn sẽ nhận được bộ lọc xác định, nhưng điều này có thể thay đổi hoàn toàn cấu hình hiệu suất của giải pháp.

Cuối cùng, đây là hàm T-SQL ngoài cùng, dbo.GetNums_AdamMachanic2, mà người dùng cuối gọi để lấy chuỗi số:

 TẠO HOẶC CHỨC NĂNG ALTER dbo.GetNums_AdamMachanic2 (@low AS BIGINT =1, @high AS BIGINT) BẢNG TRỞ LẠIASRETURN CHỌN Y.val AS n FROM (CHỌN max_base, base_add FROM dbo.GetNums_AdamMachanicgh2_8192_base) (@) AS X CROSS ÁP DỤNG dbo.GetNums_AdamMachanic2_8192 (X.max_base, X.base_add) NHƯ YGO 

Hàm này sử dụng toán tử ÁP DỤNG CROSS để áp dụng hàm T-SQL bên trong dbo.GetNums_AdamMachanic2_8192 trên mỗi hàng được trả về bởi hàm CLR bên trong dbo.GetNums_AdamMachanic2_8192_base.

Đầu tiên chúng ta hãy thử nghiệm giải pháp này bằng cách sử dụng kỹ thuật tổng hợp khi đơn đặt hàng không quan trọng:

 CHỌN TỐI ĐA (n) NHƯ mx TỪ dbo.GetNums_AdamMachanic2 (1, 100000000); 

Tôi nhận được kế hoạch được hiển thị trong Hình 7 cho việc thực hiện này.

Hình 7:Kế hoạch cho hàm dbo.GetNums_AdamMachanic2

Tôi nhận được số liệu thống kê về thời gian sau cho bài kiểm tra này:

SQL Server thời gian phân tích cú pháp và biên dịch :Thời gian CPU =313 ms, thời gian đã trôi qua = 339 ms .
SQL Server thời gian thực thi :Thời gian CPU =8859 ms, thời gian đã trôi qua = 8849 ms .

Không cần đọc logic.

Thời gian thực thi không tệ, nhưng hãy lưu ý thời gian biên dịch cao do hàm tạo giá trị bảng lớn được sử dụng. Bạn sẽ phải trả thời gian biên dịch cao như vậy bất kể kích thước phạm vi mà bạn yêu cầu, vì vậy điều này đặc biệt phức tạp khi sử dụng hàm với phạm vi rất nhỏ. Và giải pháp này vẫn chậm hơn so với giải pháp T-SQL.

Hãy kiểm tra chức năng với thứ tự:

 DECLARE @n AS BIGINT; CHỌN @n =n TỪ dbo.GetNums_AdamMachanic2 (1, 100000000) ĐẶT HÀNG THEO n; 

Tôi nhận được kế hoạch được hiển thị trong Hình 8 cho việc thực thi này.

Hình 8:Kế hoạch cho hàm dbo.GetNums_AdamMachanic2 với ORDER BY

Giống như với giải pháp thứ hai của Kamil, cần có sự sắp xếp rõ ràng trong kế hoạch, loại trừ một hình phạt hiệu suất đáng kể. Đây là số liệu thống kê về thời gian mà tôi nhận được cho bài kiểm tra này:

Thời gian thực thi:thời gian CPU =54891 ms, thời gian đã trôi qua = 60981 ms .

Ngoài ra, vẫn có mức phạt cao về thời gian biên dịch khoảng một phần ba giây.

Kết luận

Thật thú vị khi thử nghiệm các giải pháp dựa trên CLR cho thử thách chuỗi số vì ban đầu nhiều người cho rằng giải pháp hoạt động tốt nhất có thể sẽ là giải pháp dựa trên CLR. Kamil và Adam đã sử dụng các cách tiếp cận tương tự, với lần thử đầu tiên sử dụng một vòng lặp đơn giản để tăng bộ đếm và tạo ra một hàng với giá trị tiếp theo cho mỗi lần lặp và lần thử thứ hai phức tạp hơn kết hợp các phần CLR và T-SQL. Cá nhân tôi không cảm thấy thoải mái với thực tế là trong cả giải pháp thứ hai của Kamil và Adam, họ đều dựa trên bộ lọc TOP không xác định và khi tôi chuyển đổi nó thành bộ lọc xác định trong thử nghiệm của riêng mình, nó có tác động bất lợi đến hiệu suất của giải pháp . Either way, our two T-SQL solutions perform better than the CLR ones, and do not result in explicit sorting in the plan when you need the rows ordered. So I don’t really see the value in pursuing the CLR route any further. Figure 9 has a performance summary of the solutions that I presented in this article.

Figure 9:Time performance comparison

To me, GetNums_AlanCharlieItzikBatch should be the solution of choice when you require absolutely no I/O footprint, and GetNums_SQKWiki should be preferred when you don’t mind a small I/O footprint. Of course, we can always hope that one day Microsoft will add this critically useful tool as a built-in one, and hopefully if/when they do, it will be a performant solution that supports batch processing and parallelism. So don’t forget to vote for this feature improvement request, and maybe even add your comments for why it’s important for you.

I really enjoyed working on this series. I learned a lot during the process, and hope that you did too.


  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ập chỉ mục hoạt động như thế nào

  2. Ngăn chặn các cuộc tấn công SQL Injection với Python

  3. Ví dụ về cải thiện hiệu suất truy vấn với chỉ mục

  4. Không gian bảng SYSMGMTDATA là ĐẦY ĐỦ trong Kho lưu trữ Quản lý Cơ sở Hạ tầng Lưới (MGMTDB)

  5. Đặc quyền của người dùng cơ sở dữ liệu là gì?