Tháng trước, tôi đã đưa ra một câu đố liên quan đến việc khớp từng hàng từ một bảng với kết quả gần nhất từ bảng khác. Tôi nhận được câu đố này từ Karen Ly, một nhà phân tích thu nhập cố định Jr. tại RBC. Tôi đã đề cập đến hai giải pháp quan hệ chính kết hợp toán tử ÁP DỤNG với các truy vấn con dựa trên TOP. Giải pháp 1 luôn có tỉ lệ bậc hai. Giải pháp 2 đã làm khá tốt khi được cung cấp các chỉ số hỗ trợ tốt, nhưng không có các chỉ số đó cũng có tỷ lệ phần tư. Trong bài viết này, tôi đề cập đến các giải pháp lặp lại, mặc dù thường bị các chuyên gia SQL phản đối, nhưng nó cung cấp khả năng mở rộng tốt hơn nhiều trong trường hợp của chúng tôi ngay cả khi không có lập chỉ mục tối ưu.
Thử thách
Xin nhắc lại nhanh, thử thách của chúng tôi liên quan đến các bảng có tên T1 và T2 mà bạn tạo bằng mã sau:
SET NOCOUNT ON; IF DB_ID('testdb') IS NULL CREATE DATABASE testdb; GO USE testdb; DROP TABLE IF EXISTS dbo.T1, dbo.T2; CREATE TABLE dbo.T1 ( keycol INT NOT NULL IDENTITY CONSTRAINT PK_T1 PRIMARY KEY, val INT NOT NULL, othercols BINARY(100) NOT NULL CONSTRAINT DFT_T1_col1 DEFAULT(0xAA) ); CREATE TABLE dbo.T2 ( keycol INT NOT NULL IDENTITY CONSTRAINT PK_T2 PRIMARY KEY, val INT NOT NULL, othercols BINARY(100) NOT NULL CONSTRAINT DFT_T2_col1 DEFAULT(0xBB) );
Sau đó, bạn sử dụng mã sau để điền vào các bảng với các tập hợp nhỏ dữ liệu mẫu nhằm kiểm tra tính đúng đắn của các giải pháp của bạn:
TRUNCATE TABLE dbo.T1; TRUNCATE TABLE dbo.T2; INSERT INTO dbo.T1 (val) VALUES(1),(1),(3),(3),(5),(8),(13),(16),(18),(20),(21); INSERT INTO dbo.T2 (val) VALUES(2),(2),(7),(3),(3),(11),(11),(13),(17),(19);
Hãy nhớ lại thử thách là đối sánh với từng hàng từ T1 với hàng từ T2 nơi chênh lệch tuyệt đối giữa T2.val và T1.val là thấp nhất. Trong trường hợp quan hệ, bạn phải sử dụng val tăng dần, keycol theo thứ tự tăng dần làm dây buộc.
Đây là kết quả mong muốn cho dữ liệu mẫu đã cho:
keycol1 val1 othercols1 keycol2 val2 othercols2 ----------- ----------- ---------- ----------- ----------- ---------- 1 1 0xAA 1 2 0xBB 2 1 0xAA 1 2 0xBB 3 3 0xAA 4 3 0xBB 4 3 0xAA 4 3 0xBB 5 5 0xAA 4 3 0xBB 6 8 0xAA 3 7 0xBB 7 13 0xAA 8 13 0xBB 8 16 0xAA 9 17 0xBB 9 18 0xAA 9 17 0xBB 10 20 0xAA 10 19 0xBB 11 21 0xAA 10 19 0xBB
Để kiểm tra hiệu suất của các giải pháp, bạn cần tập hợp dữ liệu mẫu lớn hơn. Trước tiên, bạn tạo hàm trợ giúp GetNums, hàm này tạo ra một chuỗi các số nguyên trong một phạm vi được yêu cầu, bằng cách sử dụng mã sau:
DROP FUNCTION IF EXISTS dbo.GetNums; GO CREATE OR ALTER FUNCTION dbo.GetNums(@low AS BIGINT, @high AS BIGINT) RETURNS TABLE AS RETURN WITH L0 AS (SELECT c FROM (SELECT 1 UNION ALL SELECT 1) AS D(c)), L1 AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B), L2 AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B), L3 AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B), L4 AS (SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B), L5 AS (SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B), Nums AS (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum FROM L5) SELECT TOP(@high - @low + 1) @low + rownum - 1 AS n FROM Nums ORDER BY rownum; GO
Sau đó, bạn điền T1 và T2 bằng cách sử dụng mã sau, điều chỉnh các tham số cho biết số hàng và giá trị tối đa dựa trên nhu cầu của bạn:
DECLARE @numrowsT1 AS INT = 1000000, @maxvalT1 AS INT = 10000000, @numrowsT2 AS INT = 1000000, @maxvalT2 AS INT = 10000000; TRUNCATE TABLE dbo.T1; TRUNCATE TABLE dbo.T2; INSERT INTO dbo.T1 WITH(TABLOCK) (val) SELECT ABS(CHECKSUM(NEWID())) % @maxvalT1 + 1 AS val FROM dbo.GetNums(1, @numrowsT1) AS Nums; INSERT INTO dbo.T2 WITH(TABLOCK) (val) SELECT ABS(CHECKSUM(NEWID())) % @maxvalT2 + 1 AS val FROM dbo.GetNums(1, @numrowsT2) AS Nums;
Trong ví dụ này, bạn đang điền vào các bảng với 1.000.000 hàng mỗi bảng, với các giá trị trong khoảng 1 - 10.000.000 trong cột val (mật độ thấp).
Giải pháp 3, sử dụng con trỏ và biến bảng dựa trên đĩa
Một giải pháp lặp lại hiệu quả cho thử thách đối sánh gần nhất của chúng tôi dựa trên một thuật toán tương tự như thuật toán Kết hợp. Ý tưởng là chỉ áp dụng một đường chuyền có thứ tự cho mỗi bảng bằng cách sử dụng con trỏ, đánh giá thứ tự và các yếu tố liên kết trong mỗi vòng để quyết định bên nào sẽ tiến lên và khớp các hàng trên đường đi.
Việc vượt qua có thứ tự đối với mỗi bảng chắc chắn sẽ được hưởng lợi từ các chỉ mục hỗ trợ, nhưng ngụ ý của việc không có các chỉ mục đó là việc sắp xếp rõ ràng sẽ diễn ra. Điều này có nghĩa là phần sắp xếp sẽ phải chịu tỷ lệ n log n, nhưng điều đó ít nghiêm trọng hơn nhiều so với chia tỷ lệ bậc hai mà bạn nhận được từ Giải pháp 2 trong các trường hợp tương tự.
Ngoài ra, hiệu suất của Giải pháp 1 và 2 bị ảnh hưởng bởi mật độ của cột val. Với mật độ cao hơn, kế hoạch áp dụng ít hoàn tiền hơn. Ngược lại, vì các giải pháp lặp lại chỉ thực hiện một lần vượt qua mỗi đầu vào nên mật độ của cột val không phải là yếu tố ảnh hưởng đến hiệu suất.
Sử dụng mã sau để tạo các chỉ mục hỗ trợ:
CREATE INDEX idx_val_key ON dbo.T1(val, keycol) INCLUDE(othercols); CREATE INDEX idx_val_key ON dbo.T2(val, keycol) INCLUDE(othercols);
Đảm bảo rằng bạn kiểm tra các giải pháp có và không có các chỉ mục này.
Đây là mã hoàn chỉnh cho Giải pháp 3:
SET NOCOUNT ON; BEGIN TRAN; DECLARE @keycol1 AS INT, @val1 AS INT, @othercols1 AS BINARY(100), @keycol2 AS INT, @val2 AS INT, @othercols2 AS BINARY(100), @prevkeycol2 AS INT, @prevval2 AS INT, @prevothercols2 AS BINARY(100), @C1 AS CURSOR, @C2 AS CURSOR, @C1fetch_status AS INT, @C2fetch_status AS INT; DECLARE @Result AS TABLE ( keycol1 INT NOT NULL PRIMARY KEY, val1 INT NOT NULL, othercols1 BINARY(100) NOT NULL, keycol2 INT NULL, val2 INT NULL, othercols2 BINARY(100) NULL ); SET @C1 = CURSOR FORWARD_ONLY STATIC READ_ONLY FOR SELECT keycol, val, othercols FROM dbo.T1 ORDER BY val, keycol; SET @C2 = CURSOR FORWARD_ONLY STATIC READ_ONLY FOR SELECT keycol, val, othercols FROM dbo.T2 ORDER BY val, keycol; OPEN @C1; OPEN @C2; FETCH NEXT FROM @C2 INTO @keycol2, @val2, @othercols2; SET @C2fetch_status = @@fetch_status; SELECT @prevkeycol2 = @keycol2, @prevval2 = @val2, @prevothercols2 = @othercols2; FETCH NEXT FROM @C1 INTO @keycol1, @val1, @othercols1; SET @C1fetch_status = @@fetch_status; WHILE @C1fetch_status = 0 BEGIN IF @val1 <= @val2 OR @C2fetch_status <> 0 BEGIN IF ABS(@val1 - @val2) < ABS(@val1 - @prevval2) INSERT INTO @Result(keycol1, val1, othercols1, keycol2, val2, othercols2) VALUES(@keycol1, @val1, @othercols1, @keycol2, @val2, @othercols2); ELSE INSERT INTO @Result(keycol1, val1, othercols1, keycol2, val2, othercols2) VALUES(@keycol1, @val1, @othercols1, @prevkeycol2, @prevval2, @prevothercols2); FETCH NEXT FROM @C1 INTO @keycol1, @val1, @othercols1; SET @C1fetch_status = @@fetch_status; END ELSE IF @C2fetch_status = 0 BEGIN IF @val2 > @prevval2 SELECT @prevkeycol2 = @keycol2, @prevval2 = @val2, @prevothercols2 = @othercols2; FETCH NEXT FROM @C2 INTO @keycol2, @val2, @othercols2; SET @C2fetch_status = @@fetch_status; END; END; SELECT keycol1, val1, SUBSTRING(othercols1, 1, 1) AS othercols1, keycol2, val2, SUBSTRING(othercols2, 1, 1) AS othercols2 FROM @Result; COMMIT TRAN;
Mã sử dụng một biến bảng được gọi là @Result để lưu trữ các kết quả phù hợp và cuối cùng trả về chúng bằng cách truy vấn biến bảng. Lưu ý rằng mã thực hiện công việc trong một giao dịch để giảm việc ghi nhật ký.
Mã sử dụng các biến con trỏ được gọi là @ C1 và @ C2 để lần lượt lặp qua các hàng trong T1 và T2, trong cả hai trường hợp được sắp xếp theo thứ tự của val, keycol. Các biến cục bộ được sử dụng để lưu trữ các giá trị hàng hiện tại từ mỗi con trỏ (@ keycol1, @ val1 và @ othercols1 cho @ C1 và @ keycol2, @ val2 và @ othercols2 cho @ C2). Các biến cục bộ bổ sung lưu trữ các giá trị hàng trước đó từ @ C2 (@ prevkeycol2, @ prevval2 và @ prevothercols2). Các biến @ C1fetch_status và @ C2fetch_status giữ trạng thái của lần tìm nạp cuối cùng từ con trỏ tương ứng.
Sau khi khai báo và mở cả hai con trỏ, mã tìm nạp một hàng từ mỗi con trỏ vào các biến cục bộ tương ứng và ban đầu lưu trữ các giá trị hàng hiện tại từ @ C2 cũng trong các biến hàng trước đó. Sau đó, mã đi vào một vòng lặp tiếp tục chạy trong khi lần tìm nạp cuối cùng từ @ C1 thành công (@ C1fetch_status =0). Phần thân của vòng lặp áp dụng mã giả sau trong mỗi vòng:
If @val1 <= @val2 or reached end of @C2 Begin If absolute difference between @val1 and @val2 is less than between @val1 and @prevval2 Add row to @Result with current row values from @C1 and current row values from @C2 Else Add row to @Result with current row values from @C1 and previous row values from @C2 Fetch next row from @C1 End Else if last fetch from @C2 was successful Begin If @val2 > @prevval2 Set variables holding @C2’s previous row values to values of current row variables Fetch next row from @C2 End
Sau đó, mã chỉ cần truy vấn biến bảng @Result để trả về tất cả các kết quả phù hợp.
Sử dụng bộ dữ liệu mẫu lớn (1.000.000 hàng trong mỗi bảng), với lập chỉ mục tối ưu, giải pháp này mất 38 giây để hoàn thành trên hệ thống của tôi và thực hiện 28.240 lần đọc logic. Tất nhiên, tỷ lệ của giải pháp này sau đó là tuyến tính. Nếu không lập chỉ mục tối ưu, phải mất 40 giây để hoàn thành (chỉ thêm 2 giây!) Và thực hiện 29.519 lần đọc logic. Phần sắp xếp trong giải pháp này có tỷ lệ n log n.
Giải pháp 4, sử dụng con trỏ và biến bảng được tối ưu hóa bộ nhớ
Để cải thiện hiệu suất của phương pháp lặp lại, một điều bạn có thể thử là thay thế việc sử dụng biến bảng dựa trên đĩa bằng một biến bảng được tối ưu hóa bộ nhớ. Vì giải pháp liên quan đến việc ghi 1.000.000 hàng vào biến bảng, điều này có thể dẫn đến một sự cải thiện không đáng kể.
Trước tiên, bạn cần bật OLTP trong bộ nhớ trong cơ sở dữ liệu bằng cách tạo một nhóm tệp được đánh dấu là CONTAINS MEMORY_OPTIMIZED_DATA và bên trong đó là một vùng chứa trỏ đến một thư mục trong hệ thống tệp. Giả sử bạn đã tạo trước một thư mục mẹ có tên C:\ IMOLTP \, hãy sử dụng mã sau để áp dụng hai bước sau:
ALTER DATABASE testdb ADD FILEGROUP testdb_MO CONTAINS MEMORY_OPTIMIZED_DATA; ALTER DATABASE testdb ADD FILE ( NAME = testdb_dir, FILENAME = 'C:\IMOLTP\testdb_dir' ) TO FILEGROUP testdb_MO;
Bước tiếp theo là tạo loại bảng được tối ưu hóa bộ nhớ làm mẫu cho biến bảng của chúng tôi bằng cách chạy mã sau:
DROP TYPE IF EXISTS dbo.TYPE_closestmatch; GO CREATE TYPE dbo.TYPE_closestmatch AS TABLE ( keycol1 INT NOT NULL PRIMARY KEY NONCLUSTERED, val1 INT NOT NULL, othercols1 BINARY(100) NOT NULL, keycol2 INT NULL, val2 INT NULL, othercols2 BINARY(100) NULL ) WITH (MEMORY_OPTIMIZED = ON);
Sau đó, thay vì khai báo ban đầu của biến bảng @Result, bạn sẽ sử dụng mã sau:
DECLARE @Result AS dbo.TYPE_closestmatch;
Đây là mã giải pháp hoàn chỉnh:
SET NOCOUNT ON; USE testdb; BEGIN TRAN; DECLARE @keycol1 AS INT, @val1 AS INT, @othercols1 AS BINARY(100), @keycol2 AS INT, @val2 AS INT, @othercols2 AS BINARY(100), @prevkeycol2 AS INT, @prevval2 AS INT, @prevothercols2 AS BINARY(100), @C1 AS CURSOR, @C2 AS CURSOR, @C1fetch_status AS INT, @C2fetch_status AS INT; DECLARE @Result AS dbo.TYPE_closestmatch; SET @C1 = CURSOR FORWARD_ONLY STATIC READ_ONLY FOR SELECT keycol, val, othercols FROM dbo.T1 ORDER BY val, keycol; SET @C2 = CURSOR FORWARD_ONLY STATIC READ_ONLY FOR SELECT keycol, val, othercols FROM dbo.T2 ORDER BY val, keycol; OPEN @C1; OPEN @C2; FETCH NEXT FROM @C2 INTO @keycol2, @val2, @othercols2; SET @C2fetch_status = @@fetch_status; SELECT @prevkeycol2 = @keycol2, @prevval2 = @val2, @prevothercols2 = @othercols2; FETCH NEXT FROM @C1 INTO @keycol1, @val1, @othercols1; SET @C1fetch_status = @@fetch_status; WHILE @C1fetch_status = 0 BEGIN IF @val1 <= @val2 OR @C2fetch_status <> 0 BEGIN IF ABS(@val1 - @val2) < ABS(@val1 - @prevval2) INSERT INTO @Result(keycol1, val1, othercols1, keycol2, val2, othercols2) VALUES(@keycol1, @val1, @othercols1, @keycol2, @val2, @othercols2); ELSE INSERT INTO @Result(keycol1, val1, othercols1, keycol2, val2, othercols2) VALUES(@keycol1, @val1, @othercols1, @prevkeycol2, @prevval2, @prevothercols2); FETCH NEXT FROM @C1 INTO @keycol1, @val1, @othercols1; SET @C1fetch_status = @@fetch_status; END ELSE IF @C2fetch_status = 0 BEGIN IF @val2 > @prevval2 SELECT @prevkeycol2 = @keycol2, @prevval2 = @val2, @prevothercols2 = @othercols2; FETCH NEXT FROM @C2 INTO @keycol2, @val2, @othercols2; SET @C2fetch_status = @@fetch_status; END; END; SELECT keycol1, val1, SUBSTRING(othercols1, 1, 1) AS othercols1, keycol2, val2, SUBSTRING(othercols2, 1, 1) AS othercols2 FROM @Result; COMMIT TRAN;
Với việc lập chỉ mục tối ưu tại chỗ, giải pháp này mất 27 giây để hoàn thành trên máy của tôi (so với 38 giây với biến bảng dựa trên đĩa) và nếu không lập chỉ mục tối ưu, phải mất 29 giây để hoàn thành (so với 40 giây). Điều đó giảm gần 30% trong thời gian chạy.
Giải pháp 5, sử dụng SQL CLR
Một cách khác để cải thiện hơn nữa hiệu suất của phương pháp lặp là triển khai giải pháp sử dụng SQL CLR, vì phần lớn chi phí của giải pháp T-SQL là do tính kém hiệu quả của việc tìm nạp và lặp con trỏ trong T-SQL.
Đây là mã giải pháp hoàn chỉnh triển khai cùng một thuật toán mà tôi đã sử dụng trong Giải pháp 3 và 4 với C #, sử dụng các đối tượng SqlDataReader thay vì con trỏ T-SQL:
using System; using System.Data; using System.Data.SqlClient; using System.Data.SqlTypes; using Microsoft.SqlServer.Server; public partial class ClosestMatch { [SqlProcedure] public static void GetClosestMatches() { using (SqlConnection conn = new SqlConnection("data source=MyServer\\MyInstance;Database=testdb;Trusted_Connection=True;MultipleActiveResultSets=true;")) { SqlCommand comm1 = new SqlCommand(); SqlCommand comm2 = new SqlCommand(); comm1.Connection = conn; comm2.Connection = conn; comm1.CommandText = "SELECT keycol, val, othercols FROM dbo.T1 ORDER BY val, keycol;"; comm2.CommandText = "SELECT keycol, val, othercols FROM dbo.T2 ORDER BY val, keycol;"; SqlMetaData[] columns = new SqlMetaData[6]; columns[0] = new SqlMetaData("keycol1", SqlDbType.Int); columns[1] = new SqlMetaData("val1", SqlDbType.Int); columns[2] = new SqlMetaData("othercols1", SqlDbType.Binary, 100); columns[3] = new SqlMetaData("keycol2", SqlDbType.Int); columns[4] = new SqlMetaData("val2", SqlDbType.Int); columns[5] = new SqlMetaData("othercols2", SqlDbType.Binary, 100); SqlDataRecord record = new SqlDataRecord(columns); SqlContext.Pipe.SendResultsStart(record); conn.Open(); SqlDataReader reader1 = comm1.ExecuteReader(); SqlDataReader reader2 = comm2.ExecuteReader(); SqlInt32 keycol1 = SqlInt32.Null; SqlInt32 val1 = SqlInt32.Null; SqlBinary othercols1 = SqlBinary.Null; SqlInt32 keycol2 = SqlInt32.Null; SqlInt32 val2 = SqlInt32.Null; SqlBinary othercols2 = SqlBinary.Null; SqlInt32 prevkeycol2 = SqlInt32.Null; SqlInt32 prevval2 = SqlInt32.Null; SqlBinary prevothercols2 = SqlBinary.Null; Boolean reader2foundrow = reader2.Read(); if (reader2foundrow) { keycol2 = reader2.GetSqlInt32(0); val2 = reader2.GetSqlInt32(1); othercols2 = reader2.GetSqlBinary(2); prevkeycol2 = keycol2; prevval2 = val2; prevothercols2 = othercols2; } Boolean reader1foundrow = reader1.Read(); if (reader1foundrow) { keycol1 = reader1.GetSqlInt32(0); val1 = reader1.GetSqlInt32(1); othercols1 = reader1.GetSqlBinary(2); } while (reader1foundrow) { if (val1 <= val2 || !reader2foundrow) { if (Math.Abs((int)(val1 - val2)) < Math.Abs((int)(val1 - prevval2))) { record.SetSqlInt32(0, keycol1); record.SetSqlInt32(1, val1); record.SetSqlBinary(2, othercols1); record.SetSqlInt32(3, keycol2); record.SetSqlInt32(4, val2); record.SetSqlBinary(5, othercols2); SqlContext.Pipe.SendResultsRow(record); } else { record.SetSqlInt32(0, keycol1); record.SetSqlInt32(1, val1); record.SetSqlBinary(2, othercols1); record.SetSqlInt32(3, prevkeycol2); record.SetSqlInt32(4, prevval2); record.SetSqlBinary(5, prevothercols2); SqlContext.Pipe.SendResultsRow(record); } reader1foundrow = reader1.Read(); if (reader1foundrow) { keycol1 = reader1.GetSqlInt32(0); val1 = reader1.GetSqlInt32(1); othercols1 = reader1.GetSqlBinary(2); } } else if (reader2foundrow) { if (val2 > prevval2) { prevkeycol2 = keycol2; prevval2 = val2; prevothercols2 = othercols2; } reader2foundrow = reader2.Read(); if (reader2foundrow) { keycol2 = reader2.GetSqlInt32(0); val2 = reader2.GetSqlInt32(1); othercols2 = reader2.GetSqlBinary(2); } } } SqlContext.Pipe.SendResultsEnd(); } } }
Để kết nối với cơ sở dữ liệu, bạn thường sử dụng tùy chọn "context connection =true" thay vì một chuỗi kết nối đầy đủ. Rất tiếc, tùy chọn này không khả dụng khi bạn cần làm việc với nhiều bộ kết quả đang hoạt động. Giải pháp của chúng tôi mô phỏng hoạt động song song với hai con trỏ sử dụng hai đối tượng SqlDataReader và do đó bạn cần một chuỗi kết nối hoàn chỉnh, với tùy chọn MultipleActiveResultSets =true. Đây là chuỗi kết nối đầy đủ:
"data source=MyServer\\MyInstance;Database=testdb;Trusted_Connection=True;MultipleActiveResultSets=true;"
Tất nhiên trong trường hợp của bạn, bạn sẽ cần thay thế MyServer \\ MyInstance bằng tên máy chủ và phiên bản (nếu có liên quan) của bạn.
Ngoài ra, thực tế là bạn không sử dụng "context connection =true" thay vì một chuỗi kết nối rõ ràng có nghĩa là assembly cần quyền truy cập vào tài nguyên bên ngoài và do đó được tin cậy. Thông thường, bạn sẽ đạt được điều này bằng cách ký nó bằng chứng chỉ hoặc khóa bất đối xứng có đăng nhập tương ứng với quyền phù hợp hoặc đưa nó vào danh sách trắng bằng thủ tục sp_add_trusted_assembly. Để đơn giản hơn, tôi sẽ đặt tùy chọn cơ sở dữ liệu TRUSTWORTHY thành BẬT và chỉ định quyền EXTERNAL_ACCESS được đặt khi tạo hợp ngữ. Đoạn mã sau triển khai giải pháp trong cơ sở dữ liệu:
EXEC sys.sp_configure 'advanced', 1; RECONFIGURE; EXEC sys.sp_configure 'clr enabled', 1; EXEC sys.sp_configure 'clr strict security', 0; RECONFIGURE; EXEC sys.sp_configure 'advanced', 0; RECONFIGURE; ALTER DATABASE testdb SET TRUSTWORTHY ON; USE testdb; DROP PROC IF EXISTS dbo.GetClosestMatches; DROP ASSEMBLY IF EXISTS ClosestMatch; CREATE ASSEMBLY ClosestMatch FROM 'C:\ClosestMatch\ClosestMatch\bin\Debug\ClosestMatch.dll' WITH PERMISSION_SET = EXTERNAL_ACCESS; GO CREATE PROCEDURE dbo.GetClosestMatches AS EXTERNAL NAME ClosestMatch.ClosestMatch.GetClosestMatches;
Mã kích hoạt CLR trong trường hợp này, tắt tùy chọn bảo mật nghiêm ngặt CLR, đặt tùy chọn cơ sở dữ liệu TRUSTWORTHY thành BẬT, tạo hợp ngữ và tạo thủ tục GetClosestMatches.
Sử dụng mã sau để kiểm tra quy trình được lưu trữ:
EXEC dbo.GetClosestMatches;
Giải pháp CLR mất 8 giây để hoàn thành trên hệ thống của tôi với lập chỉ mục tối ưu và 9 giây mà không. Đó là một cải tiến hiệu suất khá ấn tượng so với tất cả các giải pháp khác - cả giải pháp quan hệ và lặp đi lặp lại.
Kết luận
Các giải pháp lặp đi lặp lại thường được cộng đồng SQL làm khó hiểu vì chúng không tuân theo mô hình quan hệ. Tuy nhiên, thực tế là đôi khi bạn không thể tạo ra các giải pháp quan hệ hiệu quả và hiệu suất là một ưu tiên. Sử dụng phương pháp lặp lại, bạn không bị giới hạn ở các thuật toán mà trình tối ưu hóa SQL Server có quyền truy cập, mà có thể triển khai bất kỳ thuật toán nào bạn thích. Như đã trình bày trong bài viết này, bằng cách sử dụng thuật toán giống như hợp nhất, bạn có thể đạt được nhiệm vụ chỉ với một lần vượt qua có thứ tự đối với mỗi đầu vào. Sử dụng con trỏ T-SQL và biến bảng dựa trên đĩa, bạn có được hiệu suất và tỷ lệ hợp lý. Bạn có thể cải thiện hiệu suất khoảng 30 phần trăm bằng cách chuyển sang một biến bảng được tối ưu hóa bộ nhớ và hơn thế nữa bằng cách sử dụng SQL CLR.