Cách có thể mở rộng để mô phỏng HASHBYTES bằng hàm vô hướng SQL CLR là gì?


29

Là một phần của quy trình ETL của chúng tôi, chúng tôi so sánh các hàng với phân tầng dựa trên cơ sở dữ liệu báo cáo để tìm hiểu xem có bất kỳ cột nào thực sự thay đổi kể từ khi dữ liệu được tải lần cuối không.

Việc so sánh dựa trên khóa duy nhất của bảng và một số loại băm của tất cả các cột khác. Chúng tôi hiện đang sử dụng HASHBYTESvới SHA2_256thuật toán và nhận thấy rằng nó không mở rộng trên các máy chủ lớn nếu nhiều luồng công nhân đồng thời đang gọi HASHBYTES.

Thông lượng được đo bằng băm mỗi giây không tăng quá 16 luồng đồng thời khi thử nghiệm trên máy chủ 96 lõi. Tôi kiểm tra bằng cách thay đổi số lượng MAXDOP 8truy vấn đồng thời từ 1 - 12. Thử nghiệm với MAXDOP 1cho thấy nút cổ chai có khả năng mở rộng tương tự.

Như một giải pháp thay thế, tôi muốn thử một giải pháp SQL CLR. Đây là nỗ lực của tôi để nêu các yêu cầu:

  • Hàm phải có khả năng tham gia vào các truy vấn song song
  • Hàm phải có tính xác định
  • Hàm phải lấy đầu vào của một chuỗi NVARCHARhoặc VARBINARY(tất cả các cột có liên quan được nối với nhau)
  • Kích thước đầu vào thông thường của chuỗi sẽ có độ dài 100 - 20000 ký tự. 20000 không phải là tối đa
  • Khả năng xảy ra va chạm băm sẽ gần bằng hoặc tốt hơn thuật toán MD5. CHECKSUMkhông làm việc cho chúng tôi vì có quá nhiều va chạm.
  • Hàm phải mở rộng tốt trên các máy chủ lớn (thông lượng trên mỗi luồng không nên giảm đáng kể khi số lượng luồng tăng)

Đối với Lý do ứng dụng ™, giả sử rằng tôi không thể lưu giá trị của hàm băm cho bảng báo cáo. Đó là một CCI không hỗ trợ các trình kích hoạt hoặc các cột được tính toán (có những vấn đề khác mà tôi không muốn mắc phải).

Một cách có thể mở rộng để mô phỏng HASHBYTESbằng cách sử dụng hàm SQL CLR là gì? Mục tiêu của tôi có thể được thể hiện bằng cách lấy càng nhiều giá trị băm mỗi giây trên một máy chủ lớn, do đó hiệu suất cũng rất quan trọng. Tôi rất tệ với CLR vì vậy tôi không biết làm thế nào để thực hiện điều này. Nếu nó thúc đẩy bất cứ ai trả lời, tôi dự định thêm tiền thưởng cho câu hỏi này ngay khi tôi có thể. Dưới đây là một truy vấn ví dụ minh họa rất rõ trường hợp sử dụng:

DROP TABLE IF EXISTS #CHANGED_IDS;

SELECT stg.ID INTO #CHANGED_IDS
FROM (
    SELECT ID,
    CAST( HASHBYTES ('SHA2_256', 
        CAST(FK1 AS NVARCHAR(19)) + 
        CAST(FK2 AS NVARCHAR(19)) + 
        CAST(FK3 AS NVARCHAR(19)) + 
        CAST(FK4 AS NVARCHAR(19)) + 
        CAST(FK5 AS NVARCHAR(19)) + 
        CAST(FK6 AS NVARCHAR(19)) + 
        CAST(FK7 AS NVARCHAR(19)) + 
        CAST(FK8 AS NVARCHAR(19)) + 
        CAST(FK9 AS NVARCHAR(19)) + 
        CAST(FK10 AS NVARCHAR(19)) + 
        CAST(FK11 AS NVARCHAR(19)) + 
        CAST(FK12 AS NVARCHAR(19)) + 
        CAST(FK13 AS NVARCHAR(19)) + 
        CAST(FK14 AS NVARCHAR(19)) + 
        CAST(FK15 AS NVARCHAR(19)) + 
        CAST(STR1 AS NVARCHAR(500)) +
        CAST(STR2 AS NVARCHAR(500)) +
        CAST(STR3 AS NVARCHAR(500)) +
        CAST(STR4 AS NVARCHAR(500)) +
        CAST(STR5 AS NVARCHAR(500)) +
        CAST(COMP1 AS NVARCHAR(1)) + 
        CAST(COMP2 AS NVARCHAR(1)) + 
        CAST(COMP3 AS NVARCHAR(1)) + 
        CAST(COMP4 AS NVARCHAR(1)) + 
        CAST(COMP5 AS NVARCHAR(1)))
     AS BINARY(32)) HASH1
    FROM HB_TBL WITH (TABLOCK)
) stg
INNER JOIN (
    SELECT ID,
    CAST(HASHBYTES ('SHA2_256', 
        CAST(FK1 AS NVARCHAR(19)) + 
        CAST(FK2 AS NVARCHAR(19)) + 
        CAST(FK3 AS NVARCHAR(19)) + 
        CAST(FK4 AS NVARCHAR(19)) + 
        CAST(FK5 AS NVARCHAR(19)) + 
        CAST(FK6 AS NVARCHAR(19)) + 
        CAST(FK7 AS NVARCHAR(19)) + 
        CAST(FK8 AS NVARCHAR(19)) + 
        CAST(FK9 AS NVARCHAR(19)) + 
        CAST(FK10 AS NVARCHAR(19)) + 
        CAST(FK11 AS NVARCHAR(19)) + 
        CAST(FK12 AS NVARCHAR(19)) + 
        CAST(FK13 AS NVARCHAR(19)) + 
        CAST(FK14 AS NVARCHAR(19)) + 
        CAST(FK15 AS NVARCHAR(19)) + 
        CAST(STR1 AS NVARCHAR(500)) +
        CAST(STR2 AS NVARCHAR(500)) +
        CAST(STR3 AS NVARCHAR(500)) +
        CAST(STR4 AS NVARCHAR(500)) +
        CAST(STR5 AS NVARCHAR(500)) +
        CAST(COMP1 AS NVARCHAR(1)) + 
        CAST(COMP2 AS NVARCHAR(1)) + 
        CAST(COMP3 AS NVARCHAR(1)) + 
        CAST(COMP4 AS NVARCHAR(1)) + 
        CAST(COMP5 AS NVARCHAR(1)) )
 AS BINARY(32)) HASH1
    FROM HB_TBL_2 WITH (TABLOCK)
) rpt ON rpt.ID = stg.ID
WHERE rpt.HASH1 <> stg.HASH1
OPTION (MAXDOP 8);

Để đơn giản hóa mọi thứ một chút, có lẽ tôi sẽ sử dụng một cái gì đó như sau để đo điểm chuẩn. Tôi sẽ đăng kết quả HASHBYTESvào thứ Hai:

CREATE TABLE dbo.HASH_ME (
    ID BIGINT NOT NULL,
    FK1 BIGINT NOT NULL,
    FK2 BIGINT NOT NULL,
    FK3 BIGINT NOT NULL,
    FK4 BIGINT NOT NULL,
    FK5 BIGINT NOT NULL,
    FK6 BIGINT NOT NULL,
    FK7 BIGINT NOT NULL,
    FK8 BIGINT NOT NULL,
    FK9 BIGINT NOT NULL,
    FK10 BIGINT NOT NULL,
    FK11 BIGINT NOT NULL,
    FK12 BIGINT NOT NULL,
    FK13 BIGINT NOT NULL,
    FK14 BIGINT NOT NULL,
    FK15 BIGINT NOT NULL,
    STR1 NVARCHAR(500) NOT NULL,
    STR2 NVARCHAR(500) NOT NULL,
    STR3 NVARCHAR(500) NOT NULL,
    STR4 NVARCHAR(500) NOT NULL,
    STR5 NVARCHAR(2000) NOT NULL,
    COMP1 TINYINT NOT NULL,
    COMP2 TINYINT NOT NULL,
    COMP3 TINYINT NOT NULL,
    COMP4 TINYINT NOT NULL,
    COMP5 TINYINT NOT NULL
);

INSERT INTO dbo.HASH_ME WITH (TABLOCK)
SELECT RN,
RN % 1000000, RN % 1000000, RN % 1000000, RN % 1000000, RN % 1000000,
RN % 1000000, RN % 1000000, RN % 1000000, RN % 1000000, RN % 1000000,
RN % 1000000, RN % 1000000, RN % 1000000, RN % 1000000, RN % 1000000,
REPLICATE(CHAR(65 + RN % 10 ), 30)
,REPLICATE(CHAR(65 + RN % 10 ), 30)
,REPLICATE(CHAR(65 + RN % 10 ), 30)
,REPLICATE(CHAR(65 + RN % 10 ), 30)
,REPLICATE(CHAR(65 + RN % 10 ), 1000),
0,1,0,1,0
FROM (
    SELECT TOP (100000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) q
OPTION (MAXDOP 1);

SELECT MAX(HASHBYTES('SHA2_256',
CAST(N'' AS NVARCHAR(MAX)) + N'|' +
CAST(FK1 AS NVARCHAR(19)) + N'|' +
CAST(FK2 AS NVARCHAR(19)) + N'|' +
CAST(FK3 AS NVARCHAR(19)) + N'|' +
CAST(FK4 AS NVARCHAR(19)) + N'|' +
CAST(FK5 AS NVARCHAR(19)) + N'|' +
CAST(FK6 AS NVARCHAR(19)) + N'|' +
CAST(FK7 AS NVARCHAR(19)) + N'|' +
CAST(FK8 AS NVARCHAR(19)) + N'|' +
CAST(FK9 AS NVARCHAR(19)) + N'|' +
CAST(FK10 AS NVARCHAR(19)) + N'|' +
CAST(FK11 AS NVARCHAR(19)) + N'|' +
CAST(FK12 AS NVARCHAR(19)) + N'|' +
CAST(FK13 AS NVARCHAR(19)) + N'|' +
CAST(FK14 AS NVARCHAR(19)) + N'|' +
CAST(FK15 AS NVARCHAR(19)) + N'|' +
CAST(STR1 AS NVARCHAR(500)) + N'|' +
CAST(STR2 AS NVARCHAR(500)) + N'|' +
CAST(STR3 AS NVARCHAR(500)) + N'|' +
CAST(STR4 AS NVARCHAR(500)) + N'|' +
CAST(STR5 AS NVARCHAR(2000)) + N'|' +
CAST(COMP1 AS NVARCHAR(1)) + N'|' +
CAST(COMP2 AS NVARCHAR(1)) + N'|' +
CAST(COMP3 AS NVARCHAR(1)) + N'|' +
CAST(COMP4 AS NVARCHAR(1)) + N'|' +
CAST(COMP5 AS NVARCHAR(1)) )
)
FROM dbo.HASH_ME
OPTION (MAXDOP 1);

Câu trả lời:


18

Vì bạn chỉ đang tìm kiếm thay đổi, bạn không cần hàm băm mật mã.

Bạn có thể chọn một trong những băm không mã hóa nhanh hơn trong thư viện Data.HashFunction mã nguồn mở của Brandon Dahler, được cấp phép theo giấy phép MIT cho phép và được OSI phê duyệt . SpookyHashlà một lựa chọn phổ biến.

Ví dụ thực hiện

Mã nguồn

using Microsoft.SqlServer.Server;
using System.Data.HashFunction.SpookyHash;
using System.Data.SqlTypes;

public partial class UserDefinedFunctions
{
    [SqlFunction
        (
            DataAccess = DataAccessKind.None,
            SystemDataAccess = SystemDataAccessKind.None,
            IsDeterministic = true,
            IsPrecise = true
        )
    ]
    public static byte[] SpookyHash
        (
            [SqlFacet (MaxSize = 8000)]
            SqlBinary Input
        )
    {
        ISpookyHashV2 sh = SpookyHashV2Factory.Instance.Create();
        return sh.ComputeHash(Input.Value).Hash;
    }

    [SqlFunction
        (
            DataAccess = DataAccessKind.None,
            IsDeterministic = true,
            IsPrecise = true,
            SystemDataAccess = SystemDataAccessKind.None
        )
    ]
    public static byte[] SpookyHashLOB
        (
            [SqlFacet (MaxSize = -1)]
            SqlBinary Input
        )
    {
        ISpookyHashV2 sh = SpookyHashV2Factory.Instance.Create();
        return sh.ComputeHash(Input.Value).Hash;
    }
}

Nguồn cung cấp hai hàm, một cho đầu vào từ 8000 byte trở xuống và phiên bản LOB. Phiên bản không phải LOB sẽ nhanh hơn đáng kể.

Bạn có thể bọc một nhị phân LOB COMPRESSđể có được nó dưới giới hạn 8000 byte, nếu điều đó hóa ra có giá trị về hiệu suất. Ngoài ra, bạn có thể chia LOB thành các phân đoạn phụ 8000, hoặc đơn giản là bảo lưu việc sử dụng HASHBYTEScho trường hợp LOB (vì đầu vào dài hơn có quy mô tốt hơn).

Mã dựng sẵn

Rõ ràng bạn có thể lấy gói cho chính mình và biên dịch mọi thứ, nhưng tôi đã xây dựng các hội đồng bên dưới để giúp kiểm tra nhanh dễ dàng hơn:

https://gist.github.com/QueryKiwi/365b265b476bf86754457fc9514b2300

Các hàm T-SQL

CREATE FUNCTION dbo.SpookyHash
(
    @Input varbinary(8000)
)
RETURNS binary(16)
WITH 
    RETURNS NULL ON NULL INPUT, 
    EXECUTE AS OWNER
AS EXTERNAL NAME Spooky.UserDefinedFunctions.SpookyHash;
GO
CREATE FUNCTION dbo.SpookyHashLOB
(
    @Input varbinary(max)
)
RETURNS binary(16)
WITH 
    RETURNS NULL ON NULL INPUT, 
    EXECUTE AS OWNER
AS EXTERNAL NAME Spooky.UserDefinedFunctions.SpookyHashLOB;
GO

Sử dụng

Một ví dụ sử dụng cho dữ liệu mẫu trong câu hỏi:

SELECT
    HT1.ID
FROM dbo.HB_TBL AS HT1
JOIN dbo.HB_TBL_2 AS HT2
    ON HT2.ID = HT1.ID
    AND dbo.SpookyHash
    (
        CONVERT(binary(8), HT2.FK1) + 0x7C +
        CONVERT(binary(8), HT2.FK2) + 0x7C +
        CONVERT(binary(8), HT2.FK3) + 0x7C +
        CONVERT(binary(8), HT2.FK4) + 0x7C +
        CONVERT(binary(8), HT2.FK5) + 0x7C +
        CONVERT(binary(8), HT2.FK6) + 0x7C +
        CONVERT(binary(8), HT2.FK7) + 0x7C +
        CONVERT(binary(8), HT2.FK8) + 0x7C +
        CONVERT(binary(8), HT2.FK9) + 0x7C +
        CONVERT(binary(8), HT2.FK10) + 0x7C +
        CONVERT(binary(8), HT2.FK11) + 0x7C +
        CONVERT(binary(8), HT2.FK12) + 0x7C +
        CONVERT(binary(8), HT2.FK13) + 0x7C +
        CONVERT(binary(8), HT2.FK14) + 0x7C +
        CONVERT(binary(8), HT2.FK15) + 0x7C +
        CONVERT(varbinary(1000), HT2.STR1) + 0x7C +
        CONVERT(varbinary(1000), HT2.STR2) + 0x7C +
        CONVERT(varbinary(1000), HT2.STR3) + 0x7C +
        CONVERT(varbinary(1000), HT2.STR4) + 0x7C +
        CONVERT(varbinary(1000), HT2.STR5) + 0x7C +
        CONVERT(binary(1), HT2.COMP1) + 0x7C +
        CONVERT(binary(1), HT2.COMP2) + 0x7C +
        CONVERT(binary(1), HT2.COMP3) + 0x7C +
        CONVERT(binary(1), HT2.COMP4) + 0x7C +
        CONVERT(binary(1), HT2.COMP5)
    )
    <> dbo.SpookyHash
    (
        CONVERT(binary(8), HT1.FK1) + 0x7C +
        CONVERT(binary(8), HT1.FK2) + 0x7C +
        CONVERT(binary(8), HT1.FK3) + 0x7C +
        CONVERT(binary(8), HT1.FK4) + 0x7C +
        CONVERT(binary(8), HT1.FK5) + 0x7C +
        CONVERT(binary(8), HT1.FK6) + 0x7C +
        CONVERT(binary(8), HT1.FK7) + 0x7C +
        CONVERT(binary(8), HT1.FK8) + 0x7C +
        CONVERT(binary(8), HT1.FK9) + 0x7C +
        CONVERT(binary(8), HT1.FK10) + 0x7C +
        CONVERT(binary(8), HT1.FK11) + 0x7C +
        CONVERT(binary(8), HT1.FK12) + 0x7C +
        CONVERT(binary(8), HT1.FK13) + 0x7C +
        CONVERT(binary(8), HT1.FK14) + 0x7C +
        CONVERT(binary(8), HT1.FK15) + 0x7C +
        CONVERT(varbinary(1000), HT1.STR1) + 0x7C +
        CONVERT(varbinary(1000), HT1.STR2) + 0x7C +
        CONVERT(varbinary(1000), HT1.STR3) + 0x7C +
        CONVERT(varbinary(1000), HT1.STR4) + 0x7C +
        CONVERT(varbinary(1000), HT1.STR5) + 0x7C +
        CONVERT(binary(1), HT1.COMP1) + 0x7C +
        CONVERT(binary(1), HT1.COMP2) + 0x7C +
        CONVERT(binary(1), HT1.COMP3) + 0x7C +
        CONVERT(binary(1), HT1.COMP4) + 0x7C +
        CONVERT(binary(1), HT1.COMP5)
    );

Khi sử dụng phiên bản LOB, tham số đầu tiên sẽ được truyền hoặc chuyển đổi thành varbinary(max).

Kế hoạch thực hiện

kế hoạch


An toàn ma quái

Các Data.HashFunction thư viện sử dụng một số tính năng ngôn ngữ CLR được coi là UNSAFEbởi SQL Server. Có thể viết một Spooky Hash cơ bản tương thích với SAFEtrạng thái. Một ví dụ tôi đã viết dựa trên SpookilySharp của Jon Hanna dưới đây:

https://gist.github.com/QueryKiwi/7a5bb26b0bee56f6d28a1d26669ce8f2


16

Tôi không chắc chắn liệu tính song song sẽ tốt hơn / đáng kể hơn với SQLCLR. Tuy nhiên, thực sự dễ dàng để kiểm tra vì có một hàm băm trong phiên bản Miễn phí của thư viện SQL # SQLCLR (mà tôi đã viết) được gọi là Util_HashBinary . Các thuật toán được hỗ trợ là: MD5, SHA1, SHA256, SHA384 và SHA512.

Nó nhận một VARBINARY(MAX)giá trị làm đầu vào, do đó bạn có thể nối phiên bản chuỗi của từng trường (như bạn hiện đang làm) và sau đó chuyển đổi sang VARBINARY(MAX)hoặc bạn có thể chuyển trực tiếp đến VARBINARYtừng cột và nối các giá trị được chuyển đổi (điều này có thể nhanh hơn vì bạn không xử lý chuỗi hoặc chuyển đổi thêm từ chuỗi sang VARBINARY). Dưới đây là một ví dụ hiển thị cả hai tùy chọn này. Nó cũng hiển thị HASHBYTEShàm để bạn có thể thấy rằng các giá trị giống nhau giữa nó và SQL # .Util_HashBinary .

Xin lưu ý rằng kết quả băm khi nối các VARBINARYgiá trị sẽ không khớp với kết quả băm khi nối các NVARCHARgiá trị. Điều này là do dạng nhị phân của INTgiá trị "1" là 0x00000001, trong khi dạng UTF-16LE (tức là NVARCHAR) của INTgiá trị "1" (ở dạng nhị phân vì đó là hàm băm sẽ hoạt động trên) là 0x3100.

SELECT so.[object_id],
       SQL#.Util_HashBinary(N'SHA256',
                            CONVERT(VARBINARY(MAX),
                                    CONCAT(so.[name], so.[schema_id], so.[create_date])
                                   )
                           ) AS [SQLCLR-ConcatStrings],
       HASHBYTES(N'SHA2_256',
                 CONVERT(VARBINARY(MAX),
                         CONCAT(so.[name], so.[schema_id], so.[create_date])
                        )
                ) AS [BuiltIn-ConcatStrings]
FROM sys.objects so;


SELECT so.[object_id],
       SQL#.Util_HashBinary(N'SHA256',
                            CONVERT(VARBINARY(500), so.[name]) + 
                            CONVERT(VARBINARY(500), so.[schema_id]) +
                            CONVERT(VARBINARY(500), so.[create_date])
                           ) AS [SQLCLR-ConcatVarBinaries],
       HASHBYTES(N'SHA2_256',
                 CONVERT(VARBINARY(500), so.[name]) + 
                 CONVERT(VARBINARY(500), so.[schema_id]) +
                 CONVERT(VARBINARY(500), so.[create_date])
                ) AS [BuiltIn-ConcatVarBinaries]
FROM sys.objects so;

Bạn có thể kiểm tra thứ gì đó tương đương với Spooky không LOB bằng cách sử dụng:

CREATE FUNCTION [SQL#].[Util_HashBinary8k]
(@Algorithm [nvarchar](50), @BaseData [varbinary](8000))
RETURNS [varbinary](8000) 
WITH EXECUTE AS CALLER, RETURNS NULL ON NULL INPUT
AS EXTERNAL NAME [SQL#].[UTILITY].[HashBinary];

Lưu ý: Util_HashBinary sử dụng thuật toán SHA256 được quản lý được tích hợp vào .NET và không nên sử dụng thư viện "bcrypt".

Ngoài khía cạnh của câu hỏi, có một số suy nghĩ bổ sung có thể giúp quá trình này:

Suy nghĩ bổ sung số 1 (băm tính toán trước, ít nhất là một số)

Bạn đã đề cập một vài điều:

  1. chúng tôi so sánh các hàng từ dàn với cơ sở dữ liệu báo cáo để tìm hiểu xem có bất kỳ cột nào thực sự thay đổi kể từ khi dữ liệu được tải lần cuối không.

    và:

  2. Tôi không thể lưu giá trị của hàm băm cho bảng báo cáo. Đó là CCI không hỗ trợ các trình kích hoạt hoặc cột được tính toán

    và:

  3. các bảng có thể được cập nhật bên ngoài quy trình ETL

Có vẻ như dữ liệu trong bảng báo cáo này ổn định trong một khoảng thời gian và chỉ được sửa đổi bởi quy trình ETL này.

Nếu không có gì khác sửa đổi bảng này, thì chúng tôi thực sự không cần một trình kích hoạt hoặc chế độ xem được lập chỉ mục (ban đầu tôi nghĩ rằng bạn có thể).

Vì bạn không thể sửa đổi lược đồ của bảng báo cáo, ít nhất có thể tạo một bảng có liên quan để chứa hàm băm được tính toán trước (và thời gian UTC khi nó được tính toán) không? Điều này sẽ cho phép bạn có một giá trị được tính toán trước để so sánh với lần sau, chỉ để lại giá trị đến yêu cầu tính toán hàm băm của. Điều này sẽ giảm số lượng cuộc gọi xuống một HASHBYTEShoặc SQL#.Util_HashBinarymột nửa. Bạn chỉ cần tham gia vào bảng băm này trong quá trình nhập.

Bạn cũng sẽ tạo một thủ tục được lưu trữ riêng biệt mà chỉ cần làm mới các giá trị băm của bảng này. Nó chỉ cập nhật các giá trị băm của bất kỳ hàng liên quan nào đã thay đổi thành hiện tại và cập nhật dấu thời gian cho các hàng đã sửa đổi đó. Proc này có thể / nên được thực hiện ở cuối của bất kỳ quá trình nào khác cập nhật bảng này. Nó cũng có thể được lên lịch để chạy 30 - 60 phút trước khi bắt đầu ETL này (tùy thuộc vào thời gian thực hiện và khi bất kỳ quy trình nào khác có thể chạy). Nó thậm chí có thể được thực thi thủ công nếu bạn từng nghi ngờ có thể có các hàng không đồng bộ.

Sau đó, đã lưu ý rằng:

có hơn 500 bàn

Việc nhiều bảng làm cho khó khăn hơn khi có một bảng bổ sung cho mỗi bảng để chứa các giá trị băm hiện tại, nhưng điều này là không thể vì nó có thể được viết theo kịch bản vì nó sẽ là một lược đồ chuẩn. Việc viết kịch bản sẽ chỉ cần tính đến tên bảng nguồn và khám phá (các) cột PK của bảng nguồn.

Tuy nhiên, bất kể là thuật toán băm cuối cùng chứng minh là khả năng mở rộng nhất, tôi vẫn đánh giá cao đề nghị tìm kiếm ít nhất một vài bảng (có lẽ có một số mà được MUCH lớn hơn so với phần còn lại của 500 bảng) và thiết lập một bảng liên quan đến chụp băm hiện tại để có thể biết các giá trị "hiện tại" trước quy trình ETL. Ngay cả chức năng nhanh nhất cũng không thể thực hiện được mà không bao giờ phải gọi nó ở vị trí đầu tiên ;-).

Suy nghĩ bổ sung số 2 ( VARBINARYthay vì NVARCHAR)

Bất kể SQLCLR so với tích hợp HASHBYTES, tôi vẫn khuyên bạn nên chuyển đổi trực tiếp sang VARBINARYvì điều đó sẽ nhanh hơn. Chuỗi kết nối chỉ là không hiệu quả khủng khiếp. , đó là ngoài việc chuyển đổi các giá trị không phải chuỗi thành chuỗi ở vị trí đầu tiên, điều này đòi hỏi nỗ lực thêm (tôi giả sử số lượng nỗ lực thay đổi dựa trên loại cơ sở: DATETIMEyêu cầu nhiều hơn BIGINT), trong khi chuyển đổi để VARBINARYđơn giản cung cấp cho bạn giá trị cơ bản (trong hầu hết các trường hợp).

Và trên thực tế, việc thử nghiệm cùng một bộ dữ liệu mà các thử nghiệm khác đã sử dụng và sử dụng HASHBYTES(N'SHA2_256',...)đã cho thấy mức tăng 23,415% trong tổng số băm được tính trong một phút. Và sự gia tăng đó là không làm gì hơn là sử dụng VARBINARYthay vì NVARCHAR! (Vui lòng xem câu trả lời wiki cộng đồng để biết chi tiết)

Suy nghĩ bổ sung # 3 (lưu tâm đến các tham số đầu vào)

Thử nghiệm thêm cho thấy một khu vực ảnh hưởng đến hiệu suất (trên khối lượng thực thi này) là các tham số đầu vào: số lượng và loại (s).

Hàm Util_HashBinary SQLCLR hiện có trong thư viện SQL # của tôi có hai tham số đầu vào: một VARBINARY(giá trị để băm) và một NVARCHAR(thuật toán sử dụng). Điều này là do tôi phản ánh chữ ký của HASHBYTESchức năng. Tuy nhiên, tôi thấy rằng nếu tôi loại bỏ NVARCHARtham số và tạo một hàm chỉ làm SHA256, thì hiệu suất được cải thiện khá độc đáo. Tôi giả định rằng ngay cả việc chuyển NVARCHARtham số sang INTsẽ có ích, nhưng tôi cũng cho rằng việc thậm chí không có INTtham số phụ ít nhất là nhanh hơn một chút .

Ngoài ra, SqlBytes.Valuecó thể thực hiện tốt hơn SqlBinary.Value.

Tôi đã tạo hai hàm mới: Util_HashSHA256BinaryUtil_HashSHA256Binary8k cho thử nghiệm này. Chúng sẽ được bao gồm trong bản phát hành tiếp theo của SQL # (chưa có ngày nào được đặt cho điều đó).

Tôi cũng thấy rằng phương pháp thử nghiệm có thể được cải thiện đôi chút, vì vậy tôi đã cập nhật khai thác thử nghiệm trong câu trả lời wiki cộng đồng dưới đây để bao gồm:

  1. tải trước các hội đồng SQLCLR để đảm bảo rằng chi phí thời gian tải không làm lệch kết quả.
  2. một thủ tục xác minh để kiểm tra va chạm. Nếu tìm thấy bất kỳ, nó sẽ hiển thị số lượng hàng duy nhất / riêng biệt và tổng số hàng. Điều này cho phép một người xác định xem số lượng va chạm (nếu có) có vượt quá giới hạn cho trường hợp sử dụng đã cho hay không. Một số trường hợp sử dụng có thể cho phép một số lượng nhỏ các va chạm, một số khác có thể không yêu cầu. Một chức năng siêu nhanh là vô ích nếu nó không thể phát hiện các thay đổi đến mức độ chính xác mong muốn. Ví dụ: bằng cách sử dụng khai thác thử nghiệm do OP cung cấp, tôi đã tăng số lượng hàng lên 100 nghìn hàng (ban đầu là 10k) và thấy rằng CHECKSUMđã đăng ký hơn 9k va chạm, là 9% (yike).

Suy nghĩ bổ sung # 4 ( HASHBYTES+ SQLCLR cùng nhau?)

Tùy thuộc vào vị trí nút cổ chai, thậm chí có thể giúp sử dụng kết hợp HASHBYTESUDF tích hợp và SQLCLR để thực hiện cùng một hàm băm. Nếu các hàm dựng sẵn bị ràng buộc khác / tách biệt với các hoạt động SQLCLR, thì cách tiếp cận này có thể có thể thực hiện đồng thời hơn so với từng HASHBYTEShoặc từng SQLCLR. Đó chắc chắn là giá trị thử nghiệm.

Suy nghĩ bổ sung # 5 (băm đối tượng lưu trữ?)

Bộ nhớ đệm của đối tượng thuật toán băm như được đề xuất trong câu trả lời của David Browne có vẻ thú vị, vì vậy tôi đã thử nó và tìm thấy hai điểm quan tâm sau:

  1. Vì lý do nào, nó dường như không cung cấp nhiều, nếu có, cải thiện hiệu suất. Tôi có thể đã làm một cái gì đó không chính xác, nhưng đây là những gì tôi đã cố gắng:

    static readonly ConcurrentDictionary<int, SHA256Managed> hashers =
        new ConcurrentDictionary<int, SHA256Managed>();
    
    [return: SqlFacet(MaxSize = 100)]
    [SqlFunction(IsDeterministic = true)]
    public static SqlBinary FastHash([SqlFacet(MaxSize = 1000)] SqlBytes Input)
    {
        SHA256Managed sh = hashers.GetOrAdd(Thread.CurrentThread.ManagedThreadId,
                                            i => new SHA256Managed());
    
        return sh.ComputeHash(Input.Value);
    }
  2. Các ManagedThreadIdgiá trị dường như là giống nhau cho tất cả các tài liệu tham khảo SQLCLR trong một truy vấn cụ thể. Tôi đã thử nghiệm nhiều tham chiếu cho cùng một chức năng, cũng như tham chiếu đến một chức năng khác nhau, cả 3 đều được cung cấp các giá trị đầu vào khác nhau và trả về các giá trị trả về khác nhau (nhưng dự kiến). Đối với cả hai hàm kiểm tra, đầu ra là một chuỗi bao gồm cả ManagedThreadIdbiểu diễn chuỗi cũng như kết quả băm. Các ManagedThreadIdgiá trị là như nhau cho tất cả các tài liệu tham khảo UDF trong truy vấn, và trên tất cả các dòng. Nhưng, kết quả băm là giống nhau cho cùng một chuỗi đầu vào và khác nhau cho các chuỗi đầu vào khác nhau.

    Mặc dù tôi không thấy bất kỳ kết quả sai lầm nào trong thử nghiệm của mình, nhưng điều này có làm tăng cơ hội của tình trạng cuộc đua không? Nếu khóa của từ điển giống nhau cho tất cả các đối tượng SQLCLR được gọi trong một truy vấn cụ thể, thì chúng sẽ được chia sẻ cùng một giá trị hoặc đối tượng được lưu trữ cho khóa đó, phải không? Vấn đề là, thậm chí nghĩ rằng nó dường như hoạt động ở đây (ở một mức độ nào đó, một lần nữa dường như không đạt được nhiều hiệu suất, nhưng về mặt chức năng không có gì bị phá vỡ), điều đó không cho tôi niềm tin rằng phương pháp này sẽ hoạt động trong các tình huống khác.


11

Đây không phải là một câu trả lời truyền thống, nhưng tôi nghĩ sẽ rất hữu ích khi đăng điểm chuẩn của một số kỹ thuật được đề cập cho đến nay. Tôi đang thử nghiệm trên máy chủ 96 lõi với SQL Server 2017 CU9.

Nhiều vấn đề về khả năng mở rộng được gây ra bởi các luồng đồng thời tranh chấp trên một số trạng thái toàn cầu. Ví dụ, hãy xem xét sự tranh chấp trang PFS cổ điển. Điều này có thể xảy ra nếu có quá nhiều luồng công nhân cần sửa đổi cùng một trang trong bộ nhớ. Khi mã trở nên hiệu quả hơn, nó có thể yêu cầu chốt nhanh hơn. Điều đó làm tăng sự tranh chấp. Nói một cách đơn giản, mã hiệu quả có nhiều khả năng dẫn đến các vấn đề về khả năng mở rộng vì nhà nước toàn cầu bị tranh chấp gay gắt hơn. Mã chậm ít có khả năng gây ra các vấn đề về khả năng mở rộng vì trạng thái toàn cầu không được truy cập thường xuyên.

HASHBYTESkhả năng mở rộng một phần dựa trên độ dài của chuỗi đầu vào. Lý thuyết của tôi là tại sao điều này xảy ra là việc truy cập vào một số trạng thái toàn cầu là cần thiết khi HASHBYTEShàm được gọi. Trạng thái toàn cầu dễ quan sát là một trang bộ nhớ cần được phân bổ cho mỗi cuộc gọi trên một số phiên bản của SQL Server. Điều khó quan sát hơn là có một số loại tranh chấp hệ điều hành. Kết quả là, nếu HASHBYTESđược gọi bởi mã ít thường xuyên hơn thì sự tranh chấp sẽ giảm xuống. Một cách để giảm tỷ lệ HASHBYTEScuộc gọi là tăng số lượng công việc băm cần thiết cho mỗi cuộc gọi. Công việc băm là một phần dựa trên độ dài của chuỗi đầu vào. Để tái tạo vấn đề về khả năng mở rộng tôi đã thấy trong ứng dụng tôi cần thay đổi dữ liệu demo. Một trường hợp xấu nhất hợp lý là một bảng có 21BIGINTcột. Định nghĩa của bảng được bao gồm trong mã ở phía dưới. Để giảm Local Factors ™, tôi đang sử dụng MAXDOP 1các truy vấn đồng thời hoạt động trên các bảng tương đối nhỏ. Mã điểm chuẩn nhanh của tôi là ở dưới cùng.

Lưu ý các hàm trả về độ dài băm khác nhau. MD5SpookyHashcả hai băm 128 bit, SHA256là hàm băm 256 bit.

KẾT QUẢ ( NVARCHARso với VARBINARYchuyển đổi và nối)

Để xem nếu chuyển đổi sang và ghép nối, VARBINARYcó thực sự hiệu quả / hiệu quả hơn so với NVARCHAR, một NVARCHARphiên bản của RUN_HASHBYTES_SHA2_256quy trình được lưu trữ đã được tạo từ cùng một mẫu (xem "Bước 5" trong phần MÃ SỐ BỀN VỮNG bên dưới). Sự khác biệt duy nhất là:

  1. Tên thủ tục lưu trữ kết thúc bằng _NVC
  2. BINARY(8)cho CASTchức năng đã được thay đổi thànhNVARCHAR(15)
  3. 0x7C đã được thay đổi thành N'|'

Kết quả là:

CAST(FK1 AS NVARCHAR(15)) + N'|' +

thay vì:

CAST(FK1 AS BINARY(8)) + 0x7C +

Bảng dưới đây chứa số lượng băm được thực hiện trong 1 phút. Các thử nghiệm được thực hiện trên một máy chủ khác với các thử nghiệm khác được ghi chú bên dưới.

╔════════════════╦══════════╦══════════════╗
    Datatype      Test #   Total Hashes 
╠════════════════╬══════════╬══════════════╣
 NVARCHAR               1      10200000 
 NVARCHAR               2      10300000 
 NVARCHAR         AVERAGE  * 10250000 * 
 -------------- ║ -------- ║ ------------ ║
 VARBINARY              1      12500000 
 VARBINARY              2      12800000 
 VARBINARY        AVERAGE  * 12650000 * 
╚════════════════╩══════════╩══════════════╝

Nhìn vào chỉ số trung bình, chúng ta có thể tính được lợi ích của việc chuyển sang VARBINARY:

SELECT (12650000 - 10250000) AS [IncreaseAmount],
       ROUND(((126500000 - 10250000) / 10250000) * 100.0, 3) AS [IncreasePercentage]

Điều đó trả về:

IncreaseAmount:    2400000.0
IncreasePercentage:   23.415

KẾT QUẢ (thuật toán băm và triển khai)

Bảng dưới đây chứa số lượng băm được thực hiện trong 1 phút. Ví dụ: sử dụng CHECKSUMvới 84 truy vấn đồng thời dẫn đến hơn 2 tỷ băm được thực hiện trước khi hết thời gian.

╔════════════════════╦════════════╦════════════╦════════════╗
      Function       12 threads  48 threads  84 threads 
╠════════════════════╬════════════╬════════════╬════════════╣
 CHECKSUM             281250000  1122440000  2040100000 
 HASHBYTES MD5         75940000   106190000   112750000 
 HASHBYTES SHA2_256    80210000   117080000   124790000 
 CLR Spooky           131250000   505700000   786150000 
 CLR SpookyLOB         17420000    27160000    31380000 
 SQL# MD5              17080000    26450000    29080000 
 SQL# SHA2_256         18370000    28860000    32590000 
 SQL# MD5 8k           24440000    30560000    32550000 
 SQL# SHA2_256 8k      87240000   159310000   155760000 
╚════════════════════╩════════════╩════════════╩════════════╝

Nếu bạn muốn xem các số tương tự được đo theo công việc trên mỗi luồng:

╔════════════════════╦════════════════════════════╦════════════════════════════╦════════════════════════════╗
      Function       12 threads per core-second  48 threads per core-second  84 threads per core-second 
╠════════════════════╬════════════════════════════╬════════════════════════════╬════════════════════════════╣
 CHECKSUM                                390625                      389736                      404782 
 HASHBYTES MD5                           105472                       36872                       22371 
 HASHBYTES SHA2_256                      111403                       40653                       24760 
 CLR Spooky                              182292                      175590                      155982 
 CLR SpookyLOB                            24194                        9431                        6226 
 SQL# MD5                                 23722                        9184                        5770 
 SQL# SHA2_256                            25514                       10021                        6466 
 SQL# MD5 8k                              33944                       10611                        6458 
 SQL# SHA2_256 8k                        121167                       55316                       30905 
╚════════════════════╩════════════════════════════╩════════════════════════════╩════════════════════════════╝

Một số suy nghĩ nhanh về tất cả các phương pháp:

  • CHECKSUM: khả năng mở rộng rất tốt như mong đợi
  • HASHBYTES: các vấn đề về khả năng mở rộng bao gồm một cấp phát bộ nhớ cho mỗi cuộc gọi và một lượng lớn CPU được sử dụng trong HĐH
  • Spooky: khả năng mở rộng tốt đáng ngạc nhiên
  • Spooky LOB: spinlock SOS_SELIST_SIZED_SLOCKquay vòng ngoài tầm kiểm soát. Tôi nghi ngờ đây là vấn đề chung khi chuyển LOB thông qua các chức năng CLR, nhưng tôi không chắc
  • Util_HashBinary: có vẻ như nó bị tấn công bởi cùng một spinlock. Tôi đã không nhìn vào điều này cho đến nay bởi vì có lẽ không có nhiều điều tôi có thể làm về nó:

quay khóa của bạn

  • Util_HashBinary 8k: kết quả rất đáng ngạc nhiên, không chắc chắn những gì đang xảy ra ở đây

Kết quả cuối cùng được thử nghiệm trên một máy chủ nhỏ hơn:

╔═════════════════════════╦════════════════════════╦════════════════════════╗
     Hash Algorithm       Hashes over 11 threads  Hashes over 44 threads 
╠═════════════════════════╬════════════════════════╬════════════════════════╣
 HASHBYTES SHA2_256                     85220000               167050000 
 SpookyHash                            101200000               239530000 
 Util_HashSHA256Binary8k                90590000               217170000 
 SpookyHashLOB                          23490000                38370000 
 Util_HashSHA256Binary                  23430000                36590000 
╚═════════════════════════╩════════════════════════╩════════════════════════╝

MÃ SỐ LỢI ÍCH

CÀI ĐẶT 1: Bảng và dữ liệu

DROP TABLE IF EXISTS dbo.HASH_SMALL;

CREATE TABLE dbo.HASH_SMALL (
    ID BIGINT NOT NULL,
    FK1 BIGINT NOT NULL,
    FK2 BIGINT NOT NULL,
    FK3 BIGINT NOT NULL,
    FK4 BIGINT NOT NULL,
    FK5 BIGINT NOT NULL,
    FK6 BIGINT NOT NULL,
    FK7 BIGINT NOT NULL,
    FK8 BIGINT NOT NULL,
    FK9 BIGINT NOT NULL,
    FK10 BIGINT NOT NULL,
    FK11 BIGINT NOT NULL,
    FK12 BIGINT NOT NULL,
    FK13 BIGINT NOT NULL,
    FK14 BIGINT NOT NULL,
    FK15 BIGINT NOT NULL,
    FK16 BIGINT NOT NULL,
    FK17 BIGINT NOT NULL,
    FK18 BIGINT NOT NULL,
    FK19 BIGINT NOT NULL,
    FK20 BIGINT NOT NULL
);

INSERT INTO dbo.HASH_SMALL WITH (TABLOCK)
SELECT RN,
4000000 - RN, 4000000 - RN
,200000000 - RN, 200000000 - RN
, RN % 500000 , RN % 500000 , RN % 500000
, RN % 500000 , RN % 500000 , RN % 500000 
, 100000 - RN % 100000, RN % 100000
, 100000 - RN % 100000, RN % 100000
, 100000 - RN % 100000, RN % 100000
, 100000 - RN % 100000, RN % 100000
, 100000 - RN % 100000, RN % 100000
FROM (
    SELECT TOP (10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) q
OPTION (MAXDOP 1);


DROP TABLE IF EXISTS dbo.LOG_HASHES;
CREATE TABLE dbo.LOG_HASHES (
LOG_TIME DATETIME,
HASH_ALGORITHM INT,
SESSION_ID INT,
NUM_HASHES BIGINT
);

CÀI ĐẶT 2: Proc Ex thi hành

GO
CREATE OR ALTER PROCEDURE dbo.RUN_HASHES_FOR_ONE_MINUTE (@HashAlgorithm INT)
AS
BEGIN
DECLARE @target_end_time DATETIME = DATEADD(MINUTE, 1, GETDATE()),
        @query_execution_count INT = 0;

SET NOCOUNT ON;

DECLARE @ProcName NVARCHAR(261); -- schema_name + proc_name + '[].[]'

DECLARE @RowCount INT;
SELECT @RowCount = SUM(prtn.[row_count])
FROM   sys.dm_db_partition_stats prtn
WHERE  prtn.[object_id] = OBJECT_ID(N'dbo.HASH_SMALL')
AND    prtn.[index_id] < 2;


-- Load assembly if not loaded to prevent load time from skewing results
DECLARE @OptionalInitSQL NVARCHAR(MAX);
SET @OptionalInitSQL = CASE @HashAlgorithm
       WHEN 1 THEN N'SELECT @Dummy = dbo.SpookyHash(0x1234);'
       WHEN 2 THEN N'' -- HASHBYTES
       WHEN 3 THEN N'' -- HASHBYTES
       WHEN 4 THEN N'' -- CHECKSUM
       WHEN 5 THEN N'SELECT @Dummy = dbo.SpookyHashLOB(0x1234);'
       WHEN 6 THEN N'SELECT @Dummy = SQL#.Util_HashBinary(N''MD5'', 0x1234);'
       WHEN 7 THEN N'SELECT @Dummy = SQL#.Util_HashBinary(N''SHA256'', 0x1234);'
       WHEN 8 THEN N'SELECT @Dummy = SQL#.Util_HashBinary8k(N''MD5'', 0x1234);'
       WHEN 9 THEN N'SELECT @Dummy = SQL#.Util_HashBinary8k(N''SHA256'', 0x1234);'
/* -- BETA / non-public code
       WHEN 10 THEN N'SELECT @Dummy = SQL#.Util_HashSHA256Binary8k(0x1234);'
       WHEN 11 THEN N'SELECT @Dummy = SQL#.Util_HashSHA256Binary(0x1234);'
*/
   END;


IF (RTRIM(@OptionalInitSQL) <> N'')
BEGIN
    SET @OptionalInitSQL = N'
SET NOCOUNT ON;
DECLARE @Dummy VARBINARY(100);
' + @OptionalInitSQL;

    RAISERROR(N'** Executing optional initialization code:', 10, 1) WITH NOWAIT;
    RAISERROR(@OptionalInitSQL, 10, 1) WITH NOWAIT;
    EXEC (@OptionalInitSQL);
    RAISERROR(N'-------------------------------------------', 10, 1) WITH NOWAIT;
END;


SET @ProcName = CASE @HashAlgorithm
                    WHEN 1 THEN N'dbo.RUN_SpookyHash'
                    WHEN 2 THEN N'dbo.RUN_HASHBYTES_MD5'
                    WHEN 3 THEN N'dbo.RUN_HASHBYTES_SHA2_256'
                    WHEN 4 THEN N'dbo.RUN_CHECKSUM'
                    WHEN 5 THEN N'dbo.RUN_SpookyHashLOB'
                    WHEN 6 THEN N'dbo.RUN_SR_MD5'
                    WHEN 7 THEN N'dbo.RUN_SR_SHA256'
                    WHEN 8 THEN N'dbo.RUN_SR_MD5_8k'
                    WHEN 9 THEN N'dbo.RUN_SR_SHA256_8k'
/* -- BETA / non-public code
                    WHEN 10 THEN N'dbo.RUN_SR_SHA256_new'
                    WHEN 11 THEN N'dbo.RUN_SR_SHA256LOB_new'
*/
                    WHEN 13 THEN N'dbo.RUN_HASHBYTES_SHA2_256_NVC'
                END;

RAISERROR(N'** Executing proc: %s', 10, 1, @ProcName) WITH NOWAIT;

WHILE GETDATE() < @target_end_time
BEGIN
    EXEC @ProcName;

    SET @query_execution_count = @query_execution_count + 1;
END;

INSERT INTO dbo.LOG_HASHES
VALUES (GETDATE(), @HashAlgorithm, @@SPID, @RowCount * @query_execution_count);

END;
GO

CÀI ĐẶT 3: Phát hiện va chạm Proc

GO
CREATE OR ALTER PROCEDURE dbo.VERIFY_NO_COLLISIONS (@HashAlgorithm INT)
AS
SET NOCOUNT ON;

DECLARE @RowCount INT;
SELECT @RowCount = SUM(prtn.[row_count])
FROM   sys.dm_db_partition_stats prtn
WHERE  prtn.[object_id] = OBJECT_ID(N'dbo.HASH_SMALL')
AND    prtn.[index_id] < 2;


DECLARE @CollisionTestRows INT;
DECLARE @CollisionTestSQL NVARCHAR(MAX);
SET @CollisionTestSQL = N'
SELECT @RowsOut = COUNT(DISTINCT '
+ CASE @HashAlgorithm
       WHEN 1 THEN N'dbo.SpookyHash('
       WHEN 2 THEN N'HASHBYTES(''MD5'','
       WHEN 3 THEN N'HASHBYTES(''SHA2_256'','
       WHEN 4 THEN N'CHECKSUM('
       WHEN 5 THEN N'dbo.SpookyHashLOB('
       WHEN 6 THEN N'SQL#.Util_HashBinary(N''MD5'','
       WHEN 7 THEN N'SQL#.Util_HashBinary(N''SHA256'','
       WHEN 8 THEN N'SQL#.[Util_HashBinary8k](N''MD5'','
       WHEN 9 THEN N'SQL#.[Util_HashBinary8k](N''SHA256'','
--/* -- BETA / non-public code
       WHEN 10 THEN N'SQL#.[Util_HashSHA256Binary8k]('
       WHEN 11 THEN N'SQL#.[Util_HashSHA256Binary]('
--*/
   END
+ N'
    CAST(FK1 AS BINARY(8)) + 0x7C +
    CAST(FK2 AS BINARY(8)) + 0x7C +
    CAST(FK3 AS BINARY(8)) + 0x7C +
    CAST(FK4 AS BINARY(8)) + 0x7C +
    CAST(FK5 AS BINARY(8)) + 0x7C +
    CAST(FK6 AS BINARY(8)) + 0x7C +
    CAST(FK7 AS BINARY(8)) + 0x7C +
    CAST(FK8 AS BINARY(8)) + 0x7C +
    CAST(FK9 AS BINARY(8)) + 0x7C +
    CAST(FK10 AS BINARY(8)) + 0x7C +
    CAST(FK11 AS BINARY(8)) + 0x7C +
    CAST(FK12 AS BINARY(8)) + 0x7C +
    CAST(FK13 AS BINARY(8)) + 0x7C +
    CAST(FK14 AS BINARY(8)) + 0x7C +
    CAST(FK15 AS BINARY(8)) + 0x7C +
    CAST(FK16 AS BINARY(8)) + 0x7C +
    CAST(FK17 AS BINARY(8)) + 0x7C +
    CAST(FK18 AS BINARY(8)) + 0x7C +
    CAST(FK19 AS BINARY(8)) + 0x7C +
    CAST(FK20 AS BINARY(8))  ))
FROM dbo.HASH_SMALL;';

PRINT @CollisionTestSQL;

EXEC sp_executesql
  @CollisionTestSQL,
  N'@RowsOut INT OUTPUT',
  @RowsOut = @CollisionTestRows OUTPUT;


IF (@CollisionTestRows <> @RowCount)
BEGIN
    RAISERROR('Collisions for algorithm: %d!!!  %d unique rows out of %d.',
    16, 1, @HashAlgorithm, @CollisionTestRows, @RowCount);
END;
GO

CÀI ĐẶT 4: Dọn dẹp (DROP All Procs Test)

DECLARE @SQL NVARCHAR(MAX) = N'';
SELECT @SQL += N'DROP PROCEDURE [dbo].' + QUOTENAME(sp.[name])
            + N';' + NCHAR(13) + NCHAR(10)
FROM  sys.objects sp
WHERE sp.[name] LIKE N'RUN[_]%'
AND   sp.[type_desc] = N'SQL_STORED_PROCEDURE'
AND   sp.[name] <> N'RUN_HASHES_FOR_ONE_MINUTE'

PRINT @SQL;

EXEC (@SQL);

CÀI ĐẶT 5: Tạo Procs thử nghiệm

SET NOCOUNT ON;

DECLARE @TestProcsToCreate TABLE
(
  ProcName sysname NOT NULL,
  CodeToExec NVARCHAR(261) NOT NULL
);
DECLARE @ProcName sysname,
        @CodeToExec NVARCHAR(261);

INSERT INTO @TestProcsToCreate VALUES
  (N'SpookyHash', N'dbo.SpookyHash('),
  (N'HASHBYTES_MD5', N'HASHBYTES(''MD5'','),
  (N'HASHBYTES_SHA2_256', N'HASHBYTES(''SHA2_256'','),
  (N'CHECKSUM', N'CHECKSUM('),
  (N'SpookyHashLOB', N'dbo.SpookyHashLOB('),
  (N'SR_MD5', N'SQL#.Util_HashBinary(N''MD5'','),
  (N'SR_SHA256', N'SQL#.Util_HashBinary(N''SHA256'','),
  (N'SR_MD5_8k', N'SQL#.[Util_HashBinary8k](N''MD5'','),
  (N'SR_SHA256_8k', N'SQL#.[Util_HashBinary8k](N''SHA256'',')
--/* -- BETA / non-public code
  , (N'SR_SHA256_new', N'SQL#.[Util_HashSHA256Binary8k]('),
  (N'SR_SHA256LOB_new', N'SQL#.[Util_HashSHA256Binary](');
--*/
DECLARE @ProcTemplate NVARCHAR(MAX),
        @ProcToCreate NVARCHAR(MAX);

SET @ProcTemplate = N'
CREATE OR ALTER PROCEDURE dbo.RUN_{{ProcName}}
AS
BEGIN
DECLARE @dummy INT;
SET NOCOUNT ON;

SELECT @dummy = COUNT({{CodeToExec}}
    CAST(FK1 AS BINARY(8)) + 0x7C +
    CAST(FK2 AS BINARY(8)) + 0x7C +
    CAST(FK3 AS BINARY(8)) + 0x7C +
    CAST(FK4 AS BINARY(8)) + 0x7C +
    CAST(FK5 AS BINARY(8)) + 0x7C +
    CAST(FK6 AS BINARY(8)) + 0x7C +
    CAST(FK7 AS BINARY(8)) + 0x7C +
    CAST(FK8 AS BINARY(8)) + 0x7C +
    CAST(FK9 AS BINARY(8)) + 0x7C +
    CAST(FK10 AS BINARY(8)) + 0x7C +
    CAST(FK11 AS BINARY(8)) + 0x7C +
    CAST(FK12 AS BINARY(8)) + 0x7C +
    CAST(FK13 AS BINARY(8)) + 0x7C +
    CAST(FK14 AS BINARY(8)) + 0x7C +
    CAST(FK15 AS BINARY(8)) + 0x7C +
    CAST(FK16 AS BINARY(8)) + 0x7C +
    CAST(FK17 AS BINARY(8)) + 0x7C +
    CAST(FK18 AS BINARY(8)) + 0x7C +
    CAST(FK19 AS BINARY(8)) + 0x7C +
    CAST(FK20 AS BINARY(8)) 
    )
    )
    FROM dbo.HASH_SMALL
    OPTION (MAXDOP 1);

END;
';

DECLARE CreateProcsCurs CURSOR READ_ONLY FORWARD_ONLY LOCAL FAST_FORWARD
FOR SELECT [ProcName], [CodeToExec]
    FROM @TestProcsToCreate;

OPEN [CreateProcsCurs];

FETCH NEXT
FROM  [CreateProcsCurs]
INTO  @ProcName, @CodeToExec;

WHILE (@@FETCH_STATUS = 0)
BEGIN
    -- First: create VARBINARY version
    SET @ProcToCreate = REPLACE(REPLACE(@ProcTemplate,
                                        N'{{ProcName}}',
                                        @ProcName),
                                N'{{CodeToExec}}',
                                @CodeToExec);

    EXEC (@ProcToCreate);

    -- Second: create NVARCHAR version (optional: built-ins only)
    IF (CHARINDEX(N'.', @CodeToExec) = 0)
    BEGIN
        SET @ProcToCreate = REPLACE(REPLACE(REPLACE(@ProcToCreate,
                                                    N'dbo.RUN_' + @ProcName,
                                                    N'dbo.RUN_' + @ProcName + N'_NVC'),
                                            N'BINARY(8)',
                                            N'NVARCHAR(15)'),
                                    N'0x7C',
                                    N'N''|''');

        EXEC (@ProcToCreate);
    END;

    FETCH NEXT
    FROM  [CreateProcsCurs]
    INTO  @ProcName, @CodeToExec;
END;

CLOSE [CreateProcsCurs];
DEALLOCATE [CreateProcsCurs];

KIỂM TRA 1: Kiểm tra va chạm

EXEC dbo.VERIFY_NO_COLLISIONS 1;
EXEC dbo.VERIFY_NO_COLLISIONS 2;
EXEC dbo.VERIFY_NO_COLLISIONS 3;
EXEC dbo.VERIFY_NO_COLLISIONS 4;
EXEC dbo.VERIFY_NO_COLLISIONS 5;
EXEC dbo.VERIFY_NO_COLLISIONS 6;
EXEC dbo.VERIFY_NO_COLLISIONS 7;
EXEC dbo.VERIFY_NO_COLLISIONS 8;
EXEC dbo.VERIFY_NO_COLLISIONS 9;
EXEC dbo.VERIFY_NO_COLLISIONS 10;
EXEC dbo.VERIFY_NO_COLLISIONS 11;

KIỂM TRA 2: Chạy thử nghiệm hiệu suất

EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 1;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 2;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 3; -- HASHBYTES('SHA2_256'
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 4;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 5;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 6;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 7;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 8;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 9;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 10;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 11;
EXEC dbo.RUN_HASHES_FOR_ONE_MINUTE 13; -- NVC version of #3


SELECT *
FROM   dbo.LOG_HASHES
ORDER BY [LOG_TIME] DESC;

VẤN ĐỀ XÁC NHẬN GIẢI QUYẾT

Trong khi tập trung vào kiểm tra hiệu năng của SQLCLR UDF đơn lẻ, hai vấn đề được thảo luận sớm không được đưa vào các thử nghiệm, nhưng lý tưởng nên được nghiên cứu để xác định phương pháp nào đáp ứng tất cả các yêu cầu.

  1. Hàm sẽ được thực hiện hai lần cho mỗi truy vấn (một lần cho hàng nhập và một lần cho hàng hiện tại). Các thử nghiệm cho đến nay chỉ tham chiếu UDF một lần trong các truy vấn thử nghiệm. Yếu tố này có thể không thay đổi thứ hạng của các tùy chọn, nhưng không nên bỏ qua, chỉ trong trường hợp.
  2. Trong một bình luận đã bị xóa, Paul White đã đề cập:

    Một nhược điểm của việc thay thế HASHBYTESbằng hàm vô hướng CLR - có vẻ như các hàm CLR không thể sử dụng chế độ hàng loạt trong khi HASHBYTEScó thể. Điều đó có thể quan trọng, hiệu suất-khôn ngoan.

    Vì vậy, đó là một cái gì đó để xem xét, và rõ ràng yêu cầu thử nghiệm. Nếu các tùy chọn SQLCLR không cung cấp bất kỳ lợi ích nào so với tích hợp sẵn HASHBYTES, thì điều đó sẽ tăng thêm sức nặng cho đề xuất của Solomon về việc bắt các giá trị băm hiện có (ít nhất là các bảng lớn nhất) vào các bảng có liên quan.


6

Bạn có thể có thể cải thiện hiệu suất và có lẽ khả năng mở rộng của tất cả các cách tiếp cận .NET bằng cách gộp và lưu trữ bất kỳ đối tượng nào được tạo trong lệnh gọi hàm. EG cho mã của Paul White ở trên:

static readonly ConcurrentDictionary<int,ISpookyHashV2> hashers = new ConcurrentDictonary<ISpookyHashV2>()
public static byte[] SpookyHash([SqlFacet (MaxSize = 8000)] SqlBinary Input)
{
    ISpookyHashV2 sh = hashers.GetOrAdd(Thread.CurrentThread.ManagedThreadId, i => SpookyHashV2Factory.Instance.Create());

    return sh.ComputeHash(Input.Value).Hash;
}

SQL CLR không khuyến khích và cố gắng ngăn sử dụng các biến tĩnh / chia sẻ, nhưng nó sẽ cho phép bạn sử dụng các biến được chia sẻ nếu bạn đánh dấu chúng là chỉ đọc. Tất nhiên, điều này là vô nghĩa khi bạn chỉ có thể gán một thể hiện duy nhất của một số loại có thể thay đổi, như ConcurrentDictionary.


thật thú vị ... chủ đề này có an toàn không nếu nó đang sử dụng cùng một ví dụ lặp đi lặp lại? Tôi biết rằng các bảng băm được quản lý có một Clear()phương pháp nhưng tôi chưa nhìn xa đến Spooky.
Solomon Rutzky

@PaulWhite và David. Tôi có thể đã làm điều gì đó sai, hoặc nó có thể là một sự khác biệt giữa SHA256ManagedSpookyHashV2, nhưng tôi đã thử điều này và không thấy nhiều, nếu có, cải thiện hiệu suất. Tôi cũng nhận thấy rằng ManagedThreadIdgiá trị này giống nhau cho tất cả các tham chiếu SQLCLR trong một truy vấn cụ thể. Tôi đã thử nghiệm nhiều tham chiếu cho cùng một chức năng, cũng như tham chiếu đến một chức năng khác nhau, cả 3 đều được cung cấp các giá trị đầu vào khác nhau và trả về các giá trị trả về khác nhau (nhưng dự kiến). Điều này sẽ không làm tăng cơ hội của một điều kiện cuộc đua? Công bằng mà nói, trong thử nghiệm của tôi, tôi đã không thấy bất kỳ.
Solomon Rutzky
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.