วิธีที่ปรับขนาดได้เพื่อจำลอง HASHBYTES โดยใช้ฟังก์ชันสเกลาร์ SQL CLR คืออะไร?


29

เป็นส่วนหนึ่งของกระบวนการ ETL ของเราเราเปรียบเทียบแถวจากการจัดเตรียมกับฐานข้อมูลการรายงานเพื่อคำนวณว่าคอลัมน์ใดมีการเปลี่ยนแปลงจริงหรือไม่นับตั้งแต่ข้อมูลถูกโหลดครั้งล่าสุด

การเปรียบเทียบจะขึ้นอยู่กับคีย์เฉพาะของตารางและการแฮชบางประเภทของคอลัมน์อื่นทั้งหมด ขณะนี้เราใช้HASHBYTESกับอัลกอริทึมและได้พบว่ามันไม่ได้ขนาดบนเซิร์ฟเวอร์ขนาดใหญ่ถ้าหลายเธรดพร้อมกันมีการโทรทั้งหมดSHA2_256HASHBYTES

ปริมาณงานที่วัดเป็นแฮชต่อวินาทีจะไม่เพิ่มเธรดที่เกิดขึ้นพร้อมกัน 16 เธรดเมื่อทดสอบบนเซิร์ฟเวอร์คอร์ 96 ฉันทดสอบโดยเปลี่ยนจำนวนMAXDOP 8ข้อความค้นหาที่เกิดขึ้นพร้อมกันจาก 1 - 12 การทดสอบด้วยMAXDOP 1แสดงให้เห็นถึงคอขวดที่สามารถปรับขยายได้แบบเดียวกัน

วิธีแก้ปัญหาฉันต้องการลองใช้โซลูชัน SQL CLR นี่คือความพยายามของฉันในการระบุข้อกำหนด:

  • ฟังก์ชั่นจะต้องสามารถมีส่วนร่วมในแบบสอบถามแบบขนาน
  • ฟังก์ชั่นจะต้องกำหนดขึ้น
  • ฟังก์ชั่นจะต้องรับอินพุตNVARCHARหรือVARBINARYสตริง (คอลัมน์ที่เกี่ยวข้องทั้งหมดถูกต่อกันเข้าด้วยกัน)
  • ขนาดอินพุตปกติของสตริงจะมีความยาว 100 - 20000 อักขระ 20000 ไม่ใช่ค่าสูงสุด
  • โอกาสของการชนกันของแฮชควรจะเท่ากับหรือดีกว่าอัลกอริทึม MD5 CHECKSUMใช้งานไม่ได้สำหรับเราเพราะมีการชนกันมากเกินไป
  • ฟังก์ชันต้องขยายขนาดได้ดีบนเซิร์ฟเวอร์ขนาดใหญ่ (ปริมาณงานต่อเธรดไม่ควรลดลงอย่างมีนัยสำคัญเมื่อจำนวนเธรดเพิ่มขึ้น)

สำหรับ Application Reasons ™ให้ถือว่าฉันไม่สามารถบันทึกค่าแฮชของตารางการรายงานได้ เป็น CCI ที่ไม่สนับสนุนทริกเกอร์หรือคอลัมน์ที่คำนวณได้ (มีปัญหาอื่น ๆ เช่นกันที่ฉันไม่ต้องการเข้าร่วม)

วิธีที่ปรับขนาดได้เพื่อจำลองHASHBYTESโดยใช้ฟังก์ชัน SQL CLR คืออะไร เป้าหมายของฉันสามารถแสดงได้ว่าได้รับแฮ็ชมากที่สุดต่อวินาทีเท่าที่ทำได้บนเซิร์ฟเวอร์ขนาดใหญ่ดังนั้นประสิทธิภาพก็มีความสำคัญเช่นกัน ฉันแย่มากกับ CLR ดังนั้นฉันไม่รู้วิธีที่จะทำสิ่งนี้ให้สำเร็จ หากมันเป็นแรงบันดาลใจให้ทุกคนตอบฉันวางแผนที่จะเพิ่มความโปรดปรานให้กับคำถามนี้ทันทีที่ฉันสามารถ ด้านล่างนี้เป็นตัวอย่างแบบสอบถามซึ่งแสดงกรณีการใช้งานอย่างคร่าว ๆ :

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);

เพื่อให้สิ่งต่าง ๆ เล็กน้อยฉันอาจใช้สิ่งต่อไปนี้เพื่อการเปรียบเทียบ ฉันจะโพสต์ผลลัพธ์ด้วยHASHBYTESในวันจันทร์:

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);

คำตอบ:


18

เนื่องจากคุณแค่มองหาการเปลี่ยนแปลงคุณไม่จำเป็นต้องใช้ฟังก์ชันแฮชการเข้ารหัสลับ

คุณสามารถเลือกจากหนึ่งใน hashes เร็วขึ้นไม่ใช่การเข้ารหัสลับในการเปิดแหล่งที่มาData.HashFunction ห้องสมุดโดยแบรนดอน Dahler ได้รับใบอนุญาตภายใต้การอนุญาตและ OSI ได้รับการอนุมัติใบอนุญาตเอ็มไอที SpookyHashเป็นตัวเลือกยอดนิยม

ตัวอย่างการนำไปใช้

รหัสแหล่งที่มา

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;
    }
}

แหล่งที่มามีสองฟังก์ชั่นหนึ่งสำหรับอินพุตของ 8000 ไบต์หรือน้อยกว่าและรุ่น LOB เวอร์ชันที่ไม่ใช่ LOB ควรเร็วกว่าอย่างมาก

คุณอาจจะสามารถห่อเลขฐานสอง LOB COMPRESSเพื่อให้ได้ภายใต้ขีด จำกัด 8000 ไบต์หากสิ่งนั้นคุ้มค่ากับประสิทธิภาพ หรือคุณสามารถแบ่ง LOB ออกเป็นเซ็กเมนต์ย่อยขนาด 8000 ไบต์หรือเพียงสำรองการใช้HASHBYTESเคส LOB (เนื่องจากอินพุตมีขนาดที่ดีขึ้น)

รหัสที่สร้างไว้ล่วงหน้า

เห็นได้ชัดว่าคุณสามารถคว้าแพ็คเกจด้วยตัวคุณเองและรวบรวมทุกอย่าง แต่ฉันได้สร้างชุดประกอบด้านล่างเพื่อให้การทดสอบอย่างรวดเร็วง่ายขึ้น:

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

ฟังก์ชัน 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

การใช้

ตัวอย่างการใช้ให้ข้อมูลตัวอย่างในคำถาม:

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)
    );

varbinary(max)เมื่อใช้รุ่นลอบพารามิเตอร์แรกควรจะโยนหรือแปลง

แผนการดำเนินการ

วางแผน


Safe Spooky

Data.HashFunctionห้องสมุดใช้จำนวนของคุณสมบัติภาษา CLR ที่มีการพิจารณาUNSAFEโดย SQL Server มันเป็นไปได้ที่จะเขียน Spooky Hash พื้นฐานที่เข้ากันได้กับSAFEสถานะ ตัวอย่างที่ฉันเขียนจากSpookilySharpของ Jon Hannaอยู่ด้านล่าง:

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


16

ฉันไม่แน่ใจว่าการขนานจะดีกว่า SQLCLR หรือไม่ แต่ก็เป็นเรื่องง่ายที่จะทดสอบเนื่องจากมีฟังก์ชันในรุ่นฟรีของSQL #ห้องสมุด SQLCLR (ซึ่งผมเขียน) เรียกว่าUtil_HashBinary อัลกอริทึมที่รองรับคือ: MD5, SHA1, SHA256, SHA384 และ SHA512

ใช้VARBINARY(MAX)ค่าเป็นอินพุตดังนั้นคุณสามารถต่อเชื่อมสตริงเวอร์ชันของแต่ละฟิลด์ (ขณะที่คุณกำลังทำอยู่) จากนั้นแปลงเป็นVARBINARY(MAX)หรือคุณสามารถไปที่VARBINARYแต่ละคอลัมน์โดยตรงและเชื่อมค่าที่แปลงแล้ว (อาจเร็วกว่า คุณไม่ได้จัดการกับสตริงหรือการแปลงพิเศษจากสตริงเป็นVARBINARY) ด้านล่างเป็นตัวอย่างที่แสดงทั้งสองตัวเลือกเหล่านี้ นอกจากนี้ยังแสดงให้เห็นถึงHASHBYTESฟังก์ชั่นเพื่อให้คุณสามารถเห็นได้ว่าค่าจะเหมือนกันระหว่างมันและSQL # .Util_HashBinary

โปรดทราบว่าผลลัพธ์แฮชเมื่อทำการต่อVARBINARYค่าจะไม่ตรงกับผลลัพธ์แฮชเมื่อทำการต่อNVARCHARค่า นี่เป็นเพราะรูปแบบไบนารีของINTค่า "1" คือ 0x00000001 ในขณะที่รูปแบบ UTF-16LE (เช่นNVARCHAR) ของINTค่าของ "1" (ในรูปแบบไบนารีเนื่องจากเป็นสิ่งที่ฟังก์ชั่นการแฮชจะทำงาน) คือ 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;

คุณสามารถทดสอบสิ่งที่เทียบเคียงได้กับผู้ที่ไม่ใช่ LOB Spooky โดยใช้:

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];

หมายเหตุ: Util_HashBinaryใช้อัลกอริทึม SHA256 ที่ได้รับการจัดการซึ่งสร้างขึ้นใน. NET และไม่ควรใช้ไลบรารี "bcrypt"

นอกเหนือจากคำถามดังกล่าวแล้วยังมีความคิดเพิ่มเติมที่อาจช่วยกระบวนการนี้ได้:

ความคิดเพิ่มเติม # 1 (คำนวณแฮชล่วงหน้าอย่างน้อยบางรายการ)

คุณพูดถึงบางสิ่ง:

  1. เราเปรียบเทียบแถวจากการจัดเตรียมกับฐานข้อมูลการรายงานเพื่อหาว่าคอลัมน์ใดมีการเปลี่ยนแปลงจริงหรือไม่นับตั้งแต่ข้อมูลถูกโหลดครั้งล่าสุด

    และ:

  2. ฉันไม่สามารถบันทึกค่าแฮชของตารางการรายงานได้ มันเป็น CCI ซึ่งไม่สนับสนุนทริกเกอร์หรือคอลัมน์ที่คำนวณ

    และ:

  3. ตารางสามารถอัพเดตนอกกระบวนการ ETL

ดูเหมือนว่าข้อมูลในตารางการรายงานนี้มีความเสถียรเป็นระยะเวลาหนึ่งและได้รับการแก้ไขโดยกระบวนการ ETL นี้เท่านั้น

หากไม่มีสิ่งใดที่แก้ไขตารางนี้เราไม่จำเป็นต้องมีทริกเกอร์หรือมุมมองที่ทำดัชนีเลย (จริงๆแล้วฉันคิดว่าคุณน่าจะ)

เนื่องจากคุณไม่สามารถแก้ไขสคีมาของตารางการรายงานอย่างน้อยเป็นไปได้ไหมที่จะสร้างตารางที่เกี่ยวข้องเพื่อให้มีแฮชที่คำนวณล่วงหน้าได้ (และเวลา UTC ของเวลาที่คำนวณ) สิ่งนี้จะช่วยให้คุณมีค่าที่คำนวณล่วงหน้าเพื่อเปรียบเทียบกับครั้งต่อไปโดยเหลือเฉพาะค่าที่เข้ามาซึ่งต้องมีการคำนวณแฮชของ วิธีนี้จะลดจำนวนการโทรไปครึ่งหนึ่งHASHBYTESหรือSQL#.Util_HashBinaryครึ่งหนึ่ง คุณเพียงแค่เข้าร่วมตารางแฮชนี้ในระหว่างกระบวนการนำเข้า

คุณจะสร้างขั้นตอนการจัดเก็บแยกต่างหากซึ่งจะรีเฟรชแฮชของตารางนี้ เพียงแค่อัพเดตแฮชของแถวที่เกี่ยวข้องที่เปลี่ยนเป็นปัจจุบันและอัพเดตเวลาประทับสำหรับแถวที่แก้ไขเหล่านั้น proc นี้สามารถ / ควรดำเนินการเมื่อสิ้นสุดกระบวนการอื่นใดที่อัพเดตตารางนี้ นอกจากนี้ยังสามารถกำหนดให้รัน 30 - 60 นาทีก่อนเริ่ม ETL นี้ (ขึ้นอยู่กับระยะเวลาที่ใช้ในการดำเนินการและเมื่อกระบวนการอื่น ๆ เหล่านี้อาจทำงาน) สามารถดำเนินการได้ด้วยตนเองหากคุณเคยสงสัยว่าอาจมีแถวที่ไม่ซิงค์กัน

มันก็สังเกตเห็นว่า:

มีมากกว่า 500 ตาราง

ตารางจำนวนมากทำให้ยากต่อการมีตารางพิเศษสำหรับแต่ละตารางเพื่อให้มีแฮชปัจจุบัน แต่มันก็เป็นไปไม่ได้เพราะมันจะถูกเขียนสคริปต์เพราะมันจะเป็นสคีมามาตรฐาน สคริปต์จะต้องบัญชีชื่อตารางแหล่งที่มาและการค้นพบคอลัมน์ PK ตารางแหล่งที่มา

ยังคงไม่คำนึงถึงวิธีกัญชาในที่สุดพิสูจน์ให้เป็นที่ปรับขนาดได้มากที่สุดก็ยังขอแนะนำให้หาอย่างน้อยไม่กี่ตาราง (อาจจะมีบางส่วนที่มีขนาดใหญ่กว่าส่วนที่เหลือของ 500 ตาราง) และการตั้งค่าตารางที่เกี่ยวข้องกับการจับภาพ แฮชปัจจุบันดังนั้นค่า "ปัจจุบัน" สามารถทราบได้ก่อนกระบวนการ ETL แม้แต่ฟังก์ชั่นที่เร็วที่สุดก็ไม่สามารถทำการแสดงออกมาได้โดยไม่ต้องเรียกมันในตอนแรก ;-)

ความคิดเพิ่มเติม # 2 ( VARBINARYแทนNVARCHAR)

โดยไม่คำนึงถึง SQLCLR เทียบกับในตัวHASHBYTESฉันยังคงอยากจะแนะนำให้แปลงโดยตรงกับVARBINARYกับที่ควรจะได้เร็วขึ้น การต่อสตริงไม่ได้มีประสิทธิภาพมากนัก และนั่นคือนอกเหนือจากการแปลงค่าที่ไม่ใช่สตริงเป็นสตริงในสถานที่แรกซึ่งต้องใช้ความพยายามพิเศษ (ฉันถือว่าจำนวนของความพยายามแตกต่างกันไปตามประเภทฐาน: DATETIMEต้องการมากกว่าBIGINT) ในขณะที่การแปลงเพื่อVARBINARYให้คุณมีมูลค่าพื้นฐาน (ในกรณีส่วนใหญ่).

และในความเป็นจริงการทดสอบชุดข้อมูลเดียวกันกับการทดสอบอื่น ๆ ที่ใช้และการใช้HASHBYTES(N'SHA2_256',...)งานพบว่ามีแฮชทั้งหมดเพิ่มขึ้น 23.415% ที่คำนวณได้ในหนึ่งนาที และการเพิ่มขึ้นนั้นไม่ได้ทำอะไรมากไปกว่าการใช้VARBINARYแทนNVARCHAR! 😸 (โปรดดูคำตอบของวิกิชุมชนสำหรับรายละเอียด)

ความคิดเพิ่มเติม # 3 (คำนึงถึงพารามิเตอร์อินพุต)

การทดสอบเพิ่มเติมแสดงให้เห็นว่าพื้นที่หนึ่งที่มีผลกระทบต่อประสิทธิภาพ (มากกว่าปริมาณการประมวลผลนี้) คือพารามิเตอร์อินพุต: จำนวนและประเภทใด

Util_HashBinaryฟังก์ชั่น SQLCLR ที่อยู่ในห้องสมุดของฉัน SQL # มีป้อนพารามิเตอร์ที่สอง: หนึ่งVARBINARY(ค่ากัญชา) และNVARCHAR(อัลกอริทึมในการใช้งาน) นี่เป็นเพราะการสะท้อนลายเซ็นของHASHBYTESฟังก์ชั่นของฉัน อย่างไรก็ตามฉันพบว่าถ้าฉันลบNVARCHARพารามิเตอร์และสร้างฟังก์ชันที่ทำเฉพาะ SHA256 เท่านั้นประสิทธิภาพก็ดีขึ้นอย่างมาก ฉันคิดว่าแม้การเปลี่ยนNVARCHARพารามิเตอร์INTจะช่วยได้ แต่ฉันก็ยังสันนิษฐานว่าแม้การมีINTพารามิเตอร์เพิ่มเติมจะเร็วกว่าเล็กน้อยอย่างน้อย

นอกจากนี้อาจจะทำงานได้ดีกว่าSqlBytes.ValueSqlBinary.Value

ฉันสร้างฟังก์ชันใหม่สองฟังก์ชัน: Util_HashSHA256BinaryและUtil_HashSHA256Binary8kสำหรับการทดสอบนี้ สิ่งเหล่านี้จะรวมอยู่ใน SQL # รุ่นถัดไป (ยังไม่ได้กำหนดวันที่)

ฉันยังพบว่าวิธีการทดสอบนั้นสามารถปรับปรุงได้เล็กน้อยดังนั้นฉันจึงอัปเดตชุดทดสอบในคำตอบของวิกิชุมชนด้านล่างเพื่อรวม:

  1. การโหลดชุดประกอบ SQLCLR ล่วงหน้าเพื่อให้แน่ใจว่าค่าใช้จ่ายในการโหลดจะไม่บิดเบือนผลลัพธ์
  2. ขั้นตอนการตรวจสอบเพื่อตรวจสอบการชน หากพบใด ๆ ก็จะแสดงจำนวนแถวที่ไม่ซ้ำกัน / แตกต่างกันและจำนวนแถวทั้งหมด สิ่งนี้อนุญาตให้หนึ่งพิจารณาว่าจำนวนการชน (ถ้ามี) เกินขีด จำกัด สำหรับกรณีการใช้งานที่กำหนดหรือไม่ กรณีการใช้งานบางอย่างอาจอนุญาตให้มีการชนกันเล็กน้อย แต่บางกรณีอาจไม่จำเป็น ฟังก์ชั่นเร็วสุดไร้ประโยชน์หากไม่สามารถตรวจจับการเปลี่ยนแปลงในระดับความแม่นยำที่ต้องการ ตัวอย่างเช่นการใช้ชุดทดสอบทดสอบที่จัดทำโดย OP ฉันเพิ่มจำนวนแถวเป็น 100k แถว (เดิมคือ 10k) และพบว่ามีการCHECKSUMลงทะเบียนมากกว่า 9k การชนซึ่งเป็น 9% (yikes)

ความคิดเพิ่มเติม # 4 ( HASHBYTES+ SQLCLR ด้วยกันไหม)

มันอาจช่วยให้ใช้การรวมกันของบิวด์อินHASHBYTESและ SQLCLR UDF เพื่อทำแฮชเดียวกัน หากฟังก์ชันในตัวมีข้อ จำกัด แตกต่างกัน / แยกจากการดำเนินการ SQLCLR ดังนั้นวิธีการนี้อาจจะสามารถทำได้พร้อมกันมากกว่าHASHBYTESหรือ SQLCLR ทีละรายการ เป็นการทดสอบที่คุ้มค่าแน่นอน

ความคิดเพิ่มเติม # 5 (การแคชวัตถุแฮช?)

การแคชวัตถุอัลกอริทึม hashing ตามที่แนะนำในคำตอบของ David Browneดูเหมือนน่าสนใจดังนั้นฉันลองและพบจุดสนใจสองจุดต่อไปนี้:

  1. ไม่ว่าจะด้วยเหตุผลใดก็ตามดูเหมือนว่าจะไม่มีการปรับปรุงประสิทธิภาพมากนักหากมี ฉันสามารถทำสิ่งที่ไม่ถูกต้อง แต่นี่คือสิ่งที่ฉันพยายาม:

    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. ManagedThreadIdค่าดูเหมือนจะเป็นเหมือนกันสำหรับทุกอ้างอิง SQLCLR ในแบบสอบถามโดยเฉพาะอย่างยิ่ง ฉันทดสอบการอ้างอิงหลายรายการไปยังฟังก์ชั่นเดียวกันเช่นเดียวกับการอ้างอิงไปยังฟังก์ชั่นที่แตกต่างกันทั้ง 3 ได้รับค่าอินพุตที่แตกต่างกันและส่งกลับค่าส่งคืนที่แตกต่างกัน (แต่คาดว่า) สำหรับฟังก์ชั่นทดสอบทั้งสองเอาท์พุทคือสตริงที่รวมถึงการManagedThreadIdแทนค่าสตริงของผลลัพธ์แฮช ManagedThreadIdค่าเป็นเหมือนกันสำหรับทุกอ้างอิง UDF ในการสอบถามและทั่วทุกแถว แต่ผลลัพธ์แฮชจะเหมือนกันสำหรับสตริงอินพุตเดียวกันและต่างกันสำหรับสตริงอินพุตที่ต่างกัน

    ในขณะที่ฉันไม่เห็นผลลัพธ์ที่ผิดพลาดใด ๆ ในการทดสอบของฉันจะไม่เพิ่มโอกาสในการแข่งขันหรือไม่ ถ้าคีย์ของพจนานุกรมเหมือนกันสำหรับวัตถุ SQLCLR ทั้งหมดที่เรียกในการสืบค้นเฉพาะพวกเขาก็จะแบ่งปันค่าเดียวกันหรือวัตถุที่เก็บไว้สำหรับคีย์นั้นใช่ไหม? ประเด็นคือถึงแม้จะคิดว่ามันดูเหมือนว่าจะทำงานที่นี่ (ในระดับหนึ่งดูเหมือนว่าจะไม่ได้รับประสิทธิภาพมากนัก แต่ไม่มีอะไรผิดปกติ) ที่ไม่ได้ให้ความมั่นใจกับฉันว่าวิธีนี้จะทำงานในสถานการณ์อื่น ๆ


11

นี่ไม่ใช่คำตอบแบบดั้งเดิม แต่ฉันคิดว่ามันจะมีประโยชน์ในการโพสต์มาตรฐานของเทคนิคบางอย่างที่กล่าวถึง ฉันกำลังทดสอบเซิร์ฟเวอร์หลัก 96 ตัวด้วย SQL Server 2017 CU9

ปัญหาความสามารถในการปรับขนาดได้หลายอย่างเกิดจากเธรดที่เกิดขึ้นพร้อมกันซึ่งมีการแข่งขันกันทั่วโลก ตัวอย่างเช่นพิจารณาข้อขัดแย้งหน้า PFS คลาสสิก สิ่งนี้อาจเกิดขึ้นหากเธรดผู้ปฏิบัติงานจำนวนมากเกินไปจำเป็นต้องแก้ไขหน้าเดียวกันในหน่วยความจำ เมื่อรหัสมีประสิทธิภาพมากขึ้นอาจขอสลักเร็วขึ้น นั่นเป็นการเพิ่มความขัดแย้ง เพื่อให้เข้าใจง่ายโค้ดที่มีประสิทธิภาพมักจะนำไปสู่ปัญหาความสามารถในการปรับขนาดได้เนื่องจากสถานะทั่วโลกถูกโต้แย้งอย่างรุนแรงมากขึ้น รหัสช้ามีแนวโน้มน้อยที่จะทำให้เกิดปัญหาการขยายได้เนื่องจากสถานะสากลไม่ได้เข้าถึงบ่อยครั้ง

HASHBYTESความสามารถในการปรับขยายได้บางส่วนขึ้นอยู่กับความยาวของสตริงอินพุต ทฤษฎีของฉันคือสาเหตุที่สิ่งนี้เกิดขึ้นคือการเข้าถึงสถานะโกลบอลบางอย่างจำเป็นเมื่อHASHBYTESเรียกใช้ฟังก์ชัน สถานะโกลบอลที่ง่ายต่อการสังเกตคือเพจหน่วยความจำที่ต้องจัดสรรต่อการโทรใน SQL Server บางเวอร์ชัน สิ่งที่สังเกตได้ยากกว่าคือมีข้อขัดแย้งของระบบปฏิบัติการบางประเภท ผลHASHBYTESก็คือถ้ารหัสถูกเรียกน้อยกว่าการโต้แย้งก็จะลดลง วิธีหนึ่งในการลดอัตราการHASHBYTESโทรคือการเพิ่มจำนวนของการแฮชที่จำเป็นต่อการโทร งานการแฮชจะขึ้นอยู่กับความยาวของสตริงอินพุตบางส่วน ในการทำซ้ำปัญหาความสามารถในการปรับขนาดที่ฉันเห็นในแอปพลิเคชันฉันจำเป็นต้องเปลี่ยนข้อมูลการสาธิต สถานการณ์กรณีที่เลวร้ายที่สุดที่สมเหตุสมผลคือตารางที่มี 21BIGINTคอลัมน์ คำจำกัดความของตารางรวมอยู่ในรหัสที่ด้านล่าง เพื่อลด Local Factors ™ฉันใช้การMAXDOP 1ค้นหาพร้อมกันที่ทำงานบนตารางที่ค่อนข้างเล็ก รหัสมาตรฐานด่วนของฉันอยู่ที่ด้านล่าง

หมายเหตุฟังก์ชั่นคืนค่าแฮชต่างกัน MD5และSpookyHashมีทั้งแฮช 128 บิตSHA256เป็นแฮช 256 บิต

ผลลัพธ์ ( NVARCHARเทียบกับVARBINARYการแปลงและการต่อข้อมูล)

เพื่อที่จะดูว่าการแปลงไปและเชื่อมโยง, VARBINARYเป็น / performant อย่างแท้จริงที่มีประสิทธิภาพมากขึ้นกว่าที่NVARCHARเป็นNVARCHARรุ่นของRUN_HASHBYTES_SHA2_256กระบวนการจัดเก็บที่ถูกสร้างขึ้นจากแม่แบบเดียวกัน (ดู "ขั้นตอนที่ 5" ในเกณฑ์มาตรฐานรหัสด้านล่าง) ข้อแตกต่างคือ:

  1. ชื่อกระบวนงานที่เก็บไว้จะสิ้นสุดลง _NVC
  2. BINARY(8)สำหรับCASTฟังก์ชั่นก็เปลี่ยนเป็นNVARCHAR(15)
  3. 0x7C ถูกเปลี่ยนเป็น N'|'

ที่เกิดขึ้นใน:

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

แทน:

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

ตารางด้านล่างประกอบด้วยจำนวนของแฮชที่ดำเนินการใน 1 นาที ทำการทดสอบบนเซิร์ฟเวอร์ที่แตกต่างจากที่ใช้สำหรับการทดสอบอื่น ๆ ที่ระบุไว้ด้านล่าง

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

เมื่อดูที่ค่าเฉลี่ยเราสามารถคำนวณข้อดีของการเปลี่ยนเป็นVARBINARY:

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

ผลตอบแทนที่ได้:

IncreaseAmount:    2400000.0
IncreasePercentage:   23.415

ผลลัพธ์ (อัลกอริทึมแฮชและการใช้งาน)

ตารางด้านล่างประกอบด้วยจำนวนของแฮชที่ดำเนินการใน 1 นาที ตัวอย่างเช่นการใช้CHECKSUM84 คิวรีที่เกิดขึ้นพร้อมกันส่งผลให้มีการแฮชมากกว่า 2 พันล้านครั้งก่อนที่เวลาจะหมด

╔════════════════════╦════════════╦════════════╦════════════╗
      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 
╚════════════════════╩════════════╩════════════╩════════════╝

หากคุณต้องการดูตัวเลขเดียวกันที่วัดได้ในแง่ของการทำงานต่อเธรดที่สอง:

╔════════════════════╦════════════════════════════╦════════════════════════════╦════════════════════════════╗
      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 
╚════════════════════╩════════════════════════════╩════════════════════════════╩════════════════════════════╝

ความคิดสั้น ๆ เกี่ยวกับวิธีการทั้งหมด:

  • CHECKSUM: ความยืดหยุ่นที่ดีมากอย่างที่คาดไว้
  • HASHBYTES: ปัญหาเกี่ยวกับความสามารถในการขยายรวมถึงการจัดสรรหน่วยความจำหนึ่งครั้งต่อการโทรหนึ่งครั้งและ CPU จำนวนมากที่ใช้ในระบบปฏิบัติการ
  • Spooky: ความยืดหยุ่นที่ดีอย่างน่าประหลาดใจ
  • Spooky LOB: spinlock SOS_SELIST_SIZED_SLOCKหมุนออกจากการควบคุม ฉันสงสัยว่านี่เป็นปัญหาทั่วไปเกี่ยวกับการส่ง LOB ผ่านฟังก์ชั่น CLR แต่ฉันไม่แน่ใจ
  • Util_HashBinary: ดูเหมือนว่ามันจะได้รับจาก spinlock เดียวกัน ฉันยังไม่ได้พิจารณาเรื่องนี้มาก่อนเพราะอาจมีอะไรมากมายที่ฉันสามารถทำได้:

หมุนล็อคของคุณ

  • Util_HashBinary 8k: ผลลัพธ์ที่น่าประหลาดใจมากไม่แน่ใจว่าเกิดอะไรขึ้นที่นี่

ผลลัพธ์สุดท้ายทดสอบบนเซิร์ฟเวอร์ที่เล็กกว่า:

╔═════════════════════════╦════════════════════════╦════════════════════════╗
     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 
╚═════════════════════════╩════════════════════════╩════════════════════════╝

รหัสการสร้างตราสินค้า

การตั้งค่า 1: ตารางและข้อมูล

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
);

การติดตั้ง 2: โปรแกรมประมวลผลหลัก

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

การตั้งค่า 3: 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

การตั้งค่า 4: การล้างข้อมูล (DROP All Procs ทดสอบ)

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);

การติดตั้ง 5: สร้างโพรบทดสอบ

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];

การทดสอบ 1: ตรวจสอบการชน

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;

การทดสอบ 2: เรียกใช้การทดสอบประสิทธิภาพ

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;

ปัญหาการตรวจสอบเพื่อแก้ไข

ในขณะที่มุ่งเน้นไปที่การทดสอบประสิทธิภาพของเอกพจน์ SQLCLR UDF, สองประเด็นที่ถูกกล่าวถึงในช่วงต้นไม่ได้ถูกรวมเข้าไปในการทดสอบ แต่ควรที่จะสืบสวนสอบสวนเพื่อตรวจสอบว่าเป็นไปตามวิธีการทั้งหมดของความต้องการ

  1. ฟังก์ชันจะถูกดำเนินการสองครั้งต่อแต่ละแบบสอบถาม (หนึ่งครั้งสำหรับแถวนำเข้าและอีกครั้งสำหรับแถวปัจจุบัน) การทดสอบจนถึงตอนนี้ได้อ้างอิง UDF เพียงครั้งเดียวในแบบสอบถามการทดสอบ ปัจจัยนี้อาจไม่เปลี่ยนการจัดอันดับของตัวเลือก แต่ไม่ควรเพิกเฉยในกรณีนี้
  2. ในความคิดเห็นที่ถูกลบไปแล้ว Paul White ได้กล่าวถึง:

    ข้อเสียเดียวของการแทนที่HASHBYTESด้วยฟังก์ชันสเกลาร์ CLR - ปรากฏว่าฟังก์ชั่น CLR ไม่สามารถใช้โหมดแบทช์ในขณะที่HASHBYTESสามารถ นั่นอาจสำคัญสำหรับประสิทธิภาพ

    เพื่อให้เป็นสิ่งที่ต้องพิจารณาและต้องมีการทดสอบอย่างชัดเจน หากตัวเลือก SQLCLR ไม่ได้ให้ประโยชน์ใด ๆ มากกว่าแบบในตัวHASHBYTESนั่นจะเป็นการเพิ่มน้ำหนักให้กับข้อเสนอแนะของโซโลมอนในการจับแฮชที่มีอยู่ (อย่างน้อยที่สุดก็คือตารางที่ใหญ่ที่สุด) ลงในตารางที่เกี่ยวข้อง


6

คุณอาจจะสามารถปรับปรุงประสิทธิภาพและความสามารถในการปรับขนาดของวิธีการ. NET ทั้งหมดด้วยการรวมกำไรและแคชวัตถุใด ๆ ที่สร้างขึ้นในการเรียกใช้ฟังก์ชัน EG สำหรับรหัสของ Paul White ด้านบน:

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 ไม่สนับสนุนและพยายามป้องกันการใช้ตัวแปรแบบสแตติก / แบบแบ่งใช้ แต่จะช่วยให้คุณใช้ตัวแปรแบบแบ่งใช้หากคุณทำเครื่องหมายว่าเป็นแบบอ่านอย่างเดียว ConcurrentDictionaryซึ่งแน่นอนมีความหมายเช่นเดียวกับคุณสามารถกำหนดเช่นเดียวของชนิดที่ไม่แน่นอนบางอย่างเช่น


น่าสนใจ ... เธรดนี้ปลอดภัยหรือไม่หากใช้อินสแตนซ์เดียวกันซ้ำแล้วซ้ำอีก ฉันรู้ว่าแฮชที่มีการจัดการนั้นมีClear()วิธีการ แต่ฉันไม่ได้มองไปที่ Spooky
โซโลมอน Rutzky

@ พอลไวท์และเดวิด ฉันอาจทำสิ่งผิดปกติหรืออาจเป็นความแตกต่างระหว่างSHA256ManagedและSpookyHashV2แต่ฉันลองทำสิ่งนี้และไม่เห็นว่าจะมีการปรับปรุงประสิทธิภาพมากขึ้นถ้ามี ฉันยังสังเกตเห็นว่าManagedThreadIdค่านั้นเหมือนกันสำหรับการอ้างอิง SQLCLR ทั้งหมดในการสืบค้นเฉพาะ ฉันทดสอบการอ้างอิงหลายรายการไปยังฟังก์ชั่นเดียวกันเช่นเดียวกับการอ้างอิงไปยังฟังก์ชั่นที่แตกต่างกันทั้ง 3 ได้รับค่าอินพุตที่แตกต่างกันและส่งกลับค่าส่งคืนที่แตกต่างกัน (แต่คาดว่า) สิ่งนี้จะไม่เพิ่มโอกาสในการแข่งขันหรือไม่? เพื่อความเป็นธรรมในการทดสอบของฉันฉันไม่เห็นเลย
โซโลมอน Rutzky
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.