SQL CLR स्केलर फ़ंक्शन का उपयोग करके HASHBYTES का अनुकरण करने का एक स्केलेबल तरीका क्या है?


29

हमारी ईटीएल प्रक्रिया के हिस्से के रूप में, हम रिपोर्ट डेटाबेस के खिलाफ स्टेजिंग से पंक्तियों की तुलना यह पता लगाने के लिए करते हैं कि क्या कोई डेटा वास्तव में लोड होने के बाद से वास्तव में बदल गया है।

तुलना तालिका की अद्वितीय कुंजी और अन्य सभी स्तंभों के किसी प्रकार के हैश पर आधारित है। हम वर्तमान HASHBYTESमें SHA2_256एल्गोरिथ्म के साथ उपयोग करते हैं और यह पाया है कि यह बड़े सर्वर पर पैमाने पर नहीं होता है यदि कई समवर्ती कार्यकर्ता धागे सभी को बुला रहे हैं HASHBYTES

प्रति सेकंड हैश में मापा गया थ्रूपुट 96 कोर सर्वर पर परीक्षण करते समय पिछले 16 समवर्ती धागे को नहीं बढ़ाता है। मैं MAXDOP 81 - 12. से समवर्ती प्रश्नों की संख्या को बदलकर परीक्षण करता हूं । परीक्षण MAXDOP 1में समान मापनीय अड़चन दिखाई गई है।

वर्कअराउंड के रूप में मैं एक SQL CLR समाधान की कोशिश करना चाहता हूं। यहाँ आवश्यकताओं को बताने का मेरा प्रयास है:

  • फ़ंक्शन समानांतर प्रश्नों में भाग लेने में सक्षम होना चाहिए
  • फ़ंक्शन को निर्धारक होना चाहिए
  • फ़ंक्शन को एक NVARCHARया एक इनपुट लेना चाहिएVARBINARY स्ट्रिंग (सभी प्रासंगिक कॉलम एक साथ समाहित किए गए हैं)
  • स्ट्रिंग का विशिष्ट इनपुट आकार लंबाई में 100 - 20000 अक्षर होगा। 20000 अधिकतम नहीं है
  • हैश टकराव की संभावना एमडी 5 एल्गोरिथ्म की तुलना में लगभग बराबर या बेहतर होनी चाहिए। CHECKSUMहमारे लिए काम नहीं करता है क्योंकि बहुत सारे टकराव हैं।
  • फ़ंक्शन को बड़े सर्वर पर अच्छी तरह से स्केल करना चाहिए (थ्रेड प्रति थ्रेड की संख्या में कमी नहीं होनी चाहिए क्योंकि थ्रेड की संख्या बढ़ जाती है)

एप्लिकेशन कारण ™ के लिए, मान लें कि मैं रिपोर्टिंग तालिका के लिए हैश का मान नहीं बचा सकता। यह CCI है जो ट्रिगर्स या कंप्यूटेड कॉलम का समर्थन नहीं करता है (अन्य समस्याएं भी हैं जो मुझे नहीं मिलनी चाहिए)।

HASHBYTESSQL 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

चूंकि आप केवल परिवर्तनों की तलाश में हैं, इसलिए आपको क्रिप्टोग्राफ़िक हैश फ़ंक्शन की आवश्यकता नहीं है।

आप ब्रैंडन डाहलर द्वारा ओपन सोर्स Data.HashFunction लाइब्रेरी में तेजी से गैर-क्रिप्टोग्राफिक हैश में से एक से चुन सकते हैं , अनुज्ञेय और 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 बाइट्स या उससे कम इनपुट के लिए, और एक एलओबी संस्करण। गैर-एलओबी संस्करण को बहुत तेज होना चाहिए।

आप COMPRESS8000 बाइट सीमा के तहत प्राप्त करने के लिए एक एलओबी बाइनरी को लपेटने में सक्षम हो सकते हैं , अगर यह प्रदर्शन के लिए सार्थक हो। वैकल्पिक रूप से, आप LOB को सब-8000 बाइट सेगमेंट में तोड़ सकते हैं, या बस के उपयोग को आरक्षित कर सकते हैंHASHBYTES LOB केस लिए (अब इनपुट बेहतर पैमाने पर)।

पूर्व निर्मित कोड

आप स्पष्ट रूप से अपने लिए पैकेज पकड़ सकते हैं और सब कुछ संकलित कर सकते हैं, लेकिन मैंने त्वरित परीक्षण को आसान बनाने के लिए नीचे विधानसभाओं का निर्माण किया:

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

टी-एसक्यूएल फ़ंक्शन

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

LOB संस्करण का उपयोग करते समय, पहले पैरामीटर को कास्ट या परिवर्तित किया जाना चाहिए varbinary(max)

निष्पादन योजना

योजना


सुरक्षित डरावना

Data.HashFunction पुस्तकालय है कि माना जाता है CLR भाषा में अनेक सुविधाओं का उपयोग करता है UNSAFEएसक्यूएल सर्वर से। एक मूल स्पूकी हैश को SAFEस्टेटस के साथ लिखना संभव है । जॉन हैना के स्पूकीशल्प पर आधारित एक उदाहरण मैंने नीचे दिया है:

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" के मूल्य का रूप (बाइनरी फॉर्म में हैस है जो उस पर काम करेगा) 0x000000 है।

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;

आप गैर-लोब स्पूकी के उपयोग से कुछ अधिक तुलनीय परीक्षण कर सकते हैं:

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 .NET में निर्मित प्रबंधित SHA256 एल्गोरिथ्म का उपयोग करता है, और "bcrypt" लाइब्रेरी का उपयोग नहीं किया जाना चाहिए।

प्रश्न के उस पहलू से परे, कुछ अतिरिक्त विचार हैं जो इस प्रक्रिया में मदद कर सकते हैं:

अतिरिक्त विचार # 1 (पूर्व-गणना हैश, कम से कम कुछ)

आपने कुछ बातों का उल्लेख किया है:

  1. हम रिपोर्टिंग डेटाबेस के खिलाफ स्टेजिंग से पंक्तियों की तुलना यह जानने के लिए करते हैं कि क्या डेटा अंतिम बार लोड किए जाने के बाद से किसी भी कॉलम में वास्तव में बदलाव हुआ है।

    तथा:

  2. मैं रिपोर्टिंग तालिका के लिए हैश का मान नहीं बचा सकता। यह CCI है जो ट्रिगर या कंप्यूटेड कॉलम का समर्थन नहीं करता है

    तथा:

  3. टेबल को ईटीएल प्रक्रिया के बाहर अद्यतन किया जा सकता है

ऐसा लगता है कि इस रिपोर्टिंग तालिका में डेटा कुछ समय के लिए स्थिर है, और केवल इस ईटीएल प्रक्रिया द्वारा संशोधित किया गया है।

यदि इस तालिका को और कुछ नहीं संशोधित किया जाता है, तो हमें वास्तव में सभी के बाद ट्रिगर या अनुक्रमित दृश्य की आवश्यकता नहीं है (मैंने मूल रूप से सोचा था कि आप हो सकते हैं)।

चूँकि आप रिपोर्टिंग तालिका के स्कीमा को संशोधित नहीं कर सकते, तो क्या कम से कम संबंधित तालिका में पूर्व-गणना की गई हैश (और जब इसकी गणना की गई तो UTC समय शामिल है) बनाना संभव होगा? यह आपको अगली बार से तुलना करने के लिए पूर्व-परिकलित मान रखने की अनुमति देगा, केवल आने वाले मूल्य को छोड़कर जिसके पास हैश की गणना करने की आवश्यकता है। इससे कॉल की संख्या HASHBYTESया तो घट जाएगी या SQL#.Util_HashBinaryआधी हो जाएगी। आप बस आयात प्रक्रिया के दौरान हैश की इस तालिका में शामिल हो जाएंगे।

आप एक अलग संग्रहित प्रक्रिया भी बनाएंगे जो इस तालिका की हैश को ताज़ा करती है। यह बस किसी भी संबंधित पंक्ति के हैश को अपडेट करता है जो वर्तमान में बदल गया है, और उन संशोधित पंक्तियों के लिए टाइमस्टैम्प को अपडेट करता है। इस तालिका को अपडेट करने वाली किसी भी अन्य प्रक्रिया के अंत में यह खरीद / निष्पादित की जा सकती है। इस ईटीएल के शुरू होने से पहले 30 - 60 मिनट चलने का समय भी निर्धारित किया जा सकता है (यह निष्पादित करने में कितना समय लगता है और कब इनमें से कोई अन्य प्रक्रिया चल सकती है)। यहां तक ​​कि इसे मैन्युअल रूप से निष्पादित किया जा सकता है यदि आपको कभी संदेह है कि ऐसी पंक्तियां हो सकती हैं जो सिंक से बाहर हैं।

तब यह नोट किया गया था कि:

500 से अधिक टेबल हैं

कई तालिकाएँ वर्तमान हैश को शामिल करने के लिए प्रत्येक के लिए एक अतिरिक्त तालिका रखना अधिक कठिन बनाती हैं, लेकिन यह असंभव नहीं है क्योंकि इसे स्क्रिप्ट किया जा सकता है क्योंकि यह एक मानक स्कीमा होगा। स्क्रिप्टिंग को स्रोत तालिका नाम और स्रोत तालिका पीके कॉलम (s) की खोज के लिए बस आवश्यकता होगी।

फिर भी, जिसकी परवाह किए बिना हैश एल्गोरिथ्म अंततः सबसे स्केलेबल साबित होता है, मैं अभी भी बहुत कम से कम कुछ तालिकाओं को खोजने की सलाह देता हूं (शायद कुछ ऐसे भी हैं जो बाकी 500 तालिकाओं से बड़े हैं) और कब्जा करने के लिए संबंधित तालिका स्थापित करना करंट हैश करता है इसलिए "वर्तमान" मान ETL प्रक्रिया से पहले जाना जा सकता है। यहां तक ​​कि सबसे तेज़ कार्य भी नहीं कर सकता है, इसे पहली जगह में कॉल करने के लिए कभी भी प्रदर्शन न करें ;-)।

अतिरिक्त सोचा # 2 (के VARBINARYबजाय NVARCHAR)

SQLCLR बनाम बिल्ट-इन के बावजूद HASHBYTES, मैं अब भी सीधे VARBINARYउसी रूप में परिवर्तित करने की सलाह दूंगा जो तेजी से होना चाहिए। कॉन्टेनेटिंग स्ट्रिंग्स सिर्फ बहुत कुशल नहीं है। और , यह पहली जगह में गैर-स्ट्रिंग मानों को स्ट्रिंग में परिवर्तित करने के अतिरिक्त है, जिसके लिए अतिरिक्त प्रयास की आवश्यकता होती है (मुझे लगता है कि प्रयास की मात्रा आधार प्रकार के आधार पर भिन्न DATETIMEहोती है : आवश्यकता से अधिक BIGINT), जबकि परिवर्तित करने के लिए VARBINARYबस आपको अंतर्निहित मूल्य मिलता है। (अधिकतर मामलों में)।

और, वास्तव में, उसी डेटासेट का परीक्षण करना, जिसका अन्य परीक्षण करते थे, और उपयोग करते हुए HASHBYTES(N'SHA2_256',...), एक मिनट में गणना की गई कुल हैश में 23.415% की वृद्धि देखी गई। और यह वृद्धि VARBINARYइसके बजाय उपयोग करने से ज्यादा कुछ नहीं करने के लिए थी NVARCHAR! Community (कृपया विवरण के लिए सामुदायिक विकि उत्तर देखें)

अतिरिक्त सोचा # 3 (इनपुट मापदंडों के प्रति सावधान रहें)

आगे के परीक्षण से पता चला है कि एक क्षेत्र जो निष्पादन को प्रभावित करता है (निष्पादन की मात्रा पर) इनपुट पैरामीटर है: कितने और क्या प्रकार (ओं)।

Util_HashBinary एक: SQLCLR समारोह मेरी एसक्यूएल # पुस्तकालय में है कि दो इनपुट पैरामीटर है VARBINARY(हैश के मूल्य), और एक NVARCHAR(उपयोग करने के लिए एल्गोरिथ्म)। यह HASHBYTESफ़ंक्शन के हस्ताक्षर को प्रतिबिंबित करने के कारण है । हालांकि, मैंने पाया कि अगर मैंने NVARCHARपैरामीटर को हटा दिया और एक फ़ंक्शन बनाया जो केवल SHA256 किया, तो प्रदर्शन में काफी सुधार हुआ। मुझे लगता है कि यहां तक ​​कि NVARCHARपैरामीटर को स्विच करने INTसे मदद मिलेगी, लेकिन मैं यह भी मानता हूं कि अतिरिक्त INTपैरामीटर नहीं होने से भी कम से कम थोड़ा तेज है।

इसके अलावा, SqlBytes.Valueबेहतर प्रदर्शन कर सकते हैं SqlBinary.Value

मैंने इस परीक्षण के लिए दो नए कार्य बनाए: Util_HashSHA256Binary और Util_HashSHA256Binary8k । ये SQL # की अगली रिलीज़ में शामिल होंगे (इसके लिए कोई तिथि निर्धारित नहीं की गई है)।

मैंने यह भी पाया कि परीक्षण पद्धति में थोड़ा सुधार किया जा सकता है, इसलिए मैंने शामिल करने के लिए नीचे दिए गए समुदाय विकी उत्तर में परीक्षण दोहन को अद्यतन किया:

  1. SQLCLR असेंबलियों का पूर्व-लोडिंग यह सुनिश्चित करने के लिए कि ओवर टाइम ओवरहेड परिणाम को तिरछा नहीं करता है।
  2. टक्करों के लिए जाँच करने के लिए एक सत्यापन प्रक्रिया। यदि कोई पाया जाता है, तो यह अद्वितीय / विशिष्ट पंक्तियों की संख्या और कुल पंक्तियों को प्रदर्शित करता है। यह निर्धारित करने की अनुमति देता है कि क्या टकराव की संख्या (यदि कोई हो) दिए गए उपयोग के मामले की सीमा से परे है। कुछ उपयोग के मामले कम संख्या में टकराव की अनुमति दे सकते हैं, अन्य को किसी की आवश्यकता नहीं हो सकती है। एक सुपर-फास्ट फ़ंक्शन बेकार है अगर यह सटीकता के वांछित स्तर में परिवर्तन का पता नहीं लगा सकता है। उदाहरण के लिए, ओपी द्वारा प्रदान किए गए टेस्ट हार्नेस का उपयोग करते हुए, मैंने पंक्ति की संख्या को 100k पंक्तियों में बढ़ाया (यह मूल रूप से 10k था) और पाया कि CHECKSUM9k टक्करों में पंजीकृत है, जो 9% (yikes) है।

अतिरिक्त विचार # 4 ( HASHBYTES+ SQLCLR एक साथ?)

अड़चन कहां है इसके आधार पर, यह HASHBYTESएक ही हैश करने के लिए अंतर्निहित और SQLCLR UDF के संयोजन का उपयोग करने में भी मदद कर सकता है । यदि अंतर्निहित फ़ंक्शन SQLCLR परिचालनों से अलग / अलग तरह से विवश हैं, तो यह दृष्टिकोण HASHBYTESव्यक्तिगत रूप से या SQLCLR की तुलना में अधिक समवर्ती रूप से पूरा करने में सक्षम हो सकता है । यह निश्चित रूप से परीक्षण के लायक है।

अतिरिक्त विचार # 5 (हैशिंग ऑब्जेक्ट कैशिंग?)

डेविड ब्राउन के उत्तर में सुझाए गए हैशिंग एल्गोरिथ्म ऑब्जेक्ट का कैशिंग निश्चित रूप से दिलचस्प लगता है, इसलिए मैंने इसे आज़माया और निम्नलिखित दो बिंदुओं को पाया:

  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मूल्य क्वेरी में सभी यूडीएफ संदर्भ के लिए ही था, और सभी पंक्तियों में। लेकिन, हैश परिणाम एक ही इनपुट स्ट्रिंग के लिए और विभिन्न इनपुट स्ट्रिंग के लिए अलग-अलग था।

    जबकि मैंने अपने परीक्षण में कोई गलत परिणाम नहीं देखा, क्या इससे दौड़ की स्थिति की संभावना नहीं बढ़ेगी? यदि शब्दकोश की कुंजी किसी विशेष क्वेरी में कहे जाने वाले सभी SQLCLR ऑब्जेक्ट्स के लिए समान है, तो वे उस कुंजी, राइट के लिए संग्रहीत समान मान या ऑब्जेक्ट साझा करेंगे? बिंदु होने के नाते, यहां तक ​​कि यह यहां काम करने के लिए लग रहा था (एक हद तक, फिर से वहां बहुत अधिक प्रदर्शन हासिल नहीं हुआ, लेकिन कार्यात्मक रूप से कुछ भी नहीं टूटा), इससे मुझे विश्वास नहीं होता कि यह दृष्टिकोण अन्य परिदृश्यों में काम करेगा।


11

यह एक पारंपरिक जवाब नहीं है, लेकिन मुझे लगा कि यह अब तक उल्लिखित कुछ तकनीकों के बेंचमार्क पोस्ट करने में मददगार होगा। मैं SQL Server 2017 CU9 के साथ 96 कोर सर्वर पर परीक्षण कर रहा हूं।

कई स्केलेबिलिटी समस्याएं समवर्ती थ्रेड्स के कारण होती हैं, जो कुछ वैश्विक अवस्था में होती हैं। उदाहरण के लिए, क्लासिक पीएफएस पृष्ठ विवाद पर विचार करें। ऐसा हो सकता है यदि बहुत से वर्कर थ्रेड्स को एक ही पृष्ठ को मेमोरी में संशोधित करने की आवश्यकता हो। जैसे ही कोड अधिक कुशल हो जाता है, यह तेजी से कुंडी का अनुरोध कर सकता है। इससे विवाद बढ़ता है। इसे सरलता से कहने के लिए, स्केलेबिलिटी मुद्दों के लिए कुशल कोड की संभावना अधिक है क्योंकि वैश्विक राज्य अधिक गंभीर रूप से संरक्षित है। धीमे कोड के कारण स्केलेबिलिटी समस्या होने की संभावना कम होती है क्योंकि वैश्विक स्थिति तक अक्सर पहुंच नहीं होती है।

HASHBYTESस्केलेबिलिटी आंशिक रूप से इनपुट स्ट्रिंग की लंबाई पर आधारित होती है। मेरा सिद्धांत यह था कि ऐसा क्यों होता है कि HASHBYTESफ़ंक्शन को कॉल करने पर कुछ वैश्विक स्थिति तक पहुंच की आवश्यकता होती है। अवलोकन करने के लिए आसान वैश्विक स्थिति एक स्मृति पृष्ठ है जिसे SQL सर्वर के कुछ संस्करणों पर प्रति कॉल आवंटित किया जाना चाहिए। निरीक्षण करने के लिए कठिन यह है कि ओएस के कुछ प्रकार के विवाद हैं। नतीजतन, यदि HASHBYTESकोड को कम बार कहा जाता है, तो विवाद नीचे जाता है। स्तंभों की दर को कम करने का एक तरीका । तालिका की परिभाषा तल पर कोड में शामिल है। स्थानीय कारकों ™ को कम करने के लिए, मैं समवर्ती प्रश्नों का उपयोग कर रहा हूं जो अपेक्षाकृत छोटे तालिकाओं पर काम करते हैं। मेरा त्वरित बेंचमार्क कोड सबसे नीचे है।HASHBYTESकॉल प्रति कॉल पर हैशिंग कार्य की मात्रा को बढ़ाया जाए। हाशिंग कार्य आंशिक रूप से इनपुट स्ट्रिंग की लंबाई पर आधारित है। मापनीयता समस्या को पुन: उत्पन्न करने के लिए मैंने डेमो डेटा को बदलने के लिए आवश्यक एप्लिकेशन में देखा। एक उचित सबसे खराब स्थिति 21 के साथ एक तालिका हैBIGINTMAXDOP 1

ध्यान दें कि कार्य अलग-अलग हैश लंबाई लौटाते हैं। MD5और SpookyHashदोनों 128 बिट हैश हैं, SHA256एक 256 बिट हैश है।

परिणाम ( NVARCHARबनाम VARBINARYरूपांतरण और संयोजन)

यह देखने के लिए कि क्या परिवर्तित और समवर्ती करना, VARBINARYवास्तव में अधिक कुशल / प्रदर्शनकारी है 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: स्केलेबिलिटी समस्याओं में प्रति कॉल एक मेमोरी आवंटन और ओएस में बड़ी मात्रा में सीपीयू शामिल हैं
  • Spooky: आश्चर्यजनक रूप से अच्छा स्केलेबिलिटी
  • Spooky LOB: पालक SOS_SELIST_SIZED_SLOCKनियंत्रण से बाहर घूमता है । मुझे संदेह है कि यह CLR फ़ंक्शंस के माध्यम से LOBs पास करने के साथ एक सामान्य मुद्दा है, लेकिन मुझे यकीन नहीं है
  • Util_HashBinary: ऐसा लगता है कि यह एक ही स्पिनलॉक की चपेट में आ जाता है। मैंने अब तक इस पर ध्यान नहीं दिया है क्योंकि शायद बहुत कुछ ऐसा नहीं है जो मैं इसके बारे में कर सकता हूं:

अपना ताला लगाओ

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

SETUP 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

SETUP 3: टकराव का पता लगाने की प्रक्रिया

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

SETUP 4: क्लीनअप (DROP ऑल टेस्ट प्रोक्स)

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

SETUP 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. फ़ंक्शन को प्रत्येक क्वेरी (आयात पंक्ति के लिए एक बार, और वर्तमान पंक्ति के लिए एक बार) के अनुसार दो बार निष्पादित किया जाएगा। अब तक के परीक्षणों ने केवल यूडीएफ को एक बार परीक्षण प्रश्नों में संदर्भित किया है। यह कारक विकल्पों की रैंकिंग में बदलाव नहीं कर सकता है, लेकिन इसे अनदेखा नहीं किया जाना चाहिए, बस मामले में।
  2. तब से हटाए गए एक टिप्पणी में, पॉल व्हाइट ने उल्लेख किया था:

    HASHBYTESसीएलआर स्केलर फ़ंक्शन के साथ प्रतिस्थापित करने का एक नकारात्मक पक्ष - यह प्रतीत होता है कि सीएलआर फ़ंक्शन बैच मोड का उपयोग नहीं कर सकते हैं जबकि HASHBYTESकर सकते हैं। यह महत्वपूर्ण हो सकता है, प्रदर्शन-वार।

    तो यह विचार करने के लिए कुछ है, और स्पष्ट रूप से परीक्षण की आवश्यकता है। यदि SQLCLR विकल्प बिल्ट-इन पर कोई लाभ प्रदान नहीं करता है HASHBYTES, तो यह सोलोमन के मौजूदा हैश (कम से कम सबसे बड़ी तालिकाओं के लिए) को संबंधित तालिकाओं में कैप्चर करने के सुझाव के लिए वजन जोड़ता है ।


6

आप शायद प्रदर्शन में सुधार कर सकते हैं, और शायद फ़ंक्शन कॉल में बनाई गई किसी भी ऑब्जेक्ट को पूल और कैशिंग करके सभी .NET दृष्टिकोणों की मापनीयता। ऊपर ईजी पॉल व्हाइट कोड के लिए:

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

एसक्यूएल सीएलआर स्थैतिक / साझा चर का उपयोग करने से रोकने की कोशिश करता है, लेकिन यदि आप उन्हें आसानी से चिह्नित करते हैं तो यह आपको साझा चर का उपयोग करने देगा। जो निश्चित रूप से, अर्थहीन है क्योंकि आप केवल कुछ उत्परिवर्तित प्रकार के एकल उदाहरण निर्दिष्ट कर सकते हैं, जैसे ConcurrentDictionary


दिलचस्प ... क्या यह धागा सुरक्षित है अगर यह एक ही उदाहरण का बार-बार उपयोग कर रहा है? मुझे पता है कि प्रबंधित हैश में एक Clear()विधि है लेकिन मैंने उस स्पूकी में दूर तक नहीं देखा है।
सोलोमन रटज़की

@PaulWhite और डेविड। मैं कुछ गलत कर सकता था, या यह एक अंतर हो सकता है SHA256Managedऔर SpookyHashV2, लेकिन मैंने यह कोशिश की और बहुत कुछ नहीं देखा, यदि कोई हो, तो प्रदर्शन में सुधार। मैंने यह भी देखा कि ManagedThreadIdकिसी विशेष क्वेरी में सभी SQLCLR संदर्भों के लिए मान समान है। मैंने एक ही फ़ंक्शन के कई संदर्भों का परीक्षण किया, साथ ही एक अलग फ़ंक्शन के संदर्भ में, सभी 3 को अलग-अलग इनपुट मान दिए गए, और अलग-अलग (लेकिन अपेक्षित) रिटर्न मान लौटाए। क्या यह एक दौड़ की स्थिति की संभावना नहीं बढ़ाएगा? निष्पक्ष होने के लिए, मेरे परीक्षण में मैंने कोई नहीं देखा।
सोलोमन रटज़की
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.