SQL CLR skaler işlevini kullanarak HASHBYTES'i simüle etmenin ölçeklenebilir bir yolu nedir?


29

ETL sürecimizin bir parçası olarak, verilerin en son yüklenmesinden bu yana sütunlardan herhangi birinin gerçekten değişip değişmediğini anlamak için satırları raporlama veritabanına göre sıralamayı karşılaştırıyoruz.

Karşılaştırma tablonun benzersiz anahtarına ve diğer tüm sütunların bir tür karma değerine dayanmaktadır. Şu anda kullanmak HASHBYTESile SHA2_256algoritma ve birçok eşzamanlı çalışan iş parçacığı tüm satıyorum eğer büyük sunucularda ölçekli yok bulduk HASHBYTES.

96 çekirdekli bir sunucuda test edilirken, saniye başına karma olarak ölçülen iş hacmi, 16 eşzamanlı iş parçacığını geçmez. Eşzamanlı MAXDOP 8sorgu sayısını 1 - 12 arasında değiştirerek test ediyorum . Test etmek MAXDOP 1, aynı ölçeklenebilirlik darboğazını gösterdi.

Çözüm olarak, bir SQL CLR çözümü denemek istiyorum. Gereksinimleri belirtme girişim:

  • İşlev, paralel sorgulara katılabilmelidir
  • İşlev deterministik olmalı
  • İşlev bir NVARCHARveya VARBINARYdizgenin girişini almalıdır (tüm ilgili sütunlar bir araya getirilmiştir)
  • Dizginin tipik giriş boyutu 100 - 20000 karakter uzunluğunda olacaktır. 20000 maksimum değil
  • Karma çarpışma şansı, MD5 algoritmasına kabaca eşit veya daha iyi olmalıdır. CHECKSUMBizim için çalışmıyor çünkü çok fazla çarpışma var.
  • İşlev büyük sunucularda iyi ölçeklenmelidir (iş parçacığı sayısı arttıkça iş parçacığı sayısı arttıkça önemli ölçüde düşmemelidir)

Application Reasons ™ için, raporlama tablosu için karma değerinden tasarruf edemediğimi varsayalım. Tetikleyicileri veya hesaplanan sütunları desteklemeyen bir CCI'dir (içine girmek istemediğim başka sorunlar da var).

HASHBYTESSQL CLR işlevini kullanarak taklit etmenin ölçeklenebilir bir yolu nedir ? Amacım, büyük bir sunucuda olabildiğince çok sayıda hash elde etmek olarak ifade edilebilir, böylece performans da önemlidir. CLR ile çok kötüyüm, bu yüzden bunu nasıl yapacağımı bilmiyorum. Eğer kimseyi cevaplamaya motive ederse, mümkün olan en kısa sürede bu soruya bir ödül eklemeyi planlıyorum. Aşağıda kullanım durumunu çok kabaca gösteren örnek bir sorgu verilmiştir:

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

İşleri biraz basitleştirmek için, muhtemelen kıyaslama için aşağıdaki gibi bir şey kullanacağım. Sonuçları HASHBYTESpazartesi günü yayınlayacağım:

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

Yanıtlar:


18

Sadece değişiklik aradığınızdan, şifreleme karma işlevine ihtiyacınız yoktur.

İzin verilen ve OSI onaylı MIT lisansı altında lisanslı Brandon Dahler tarafından açık kaynaklı Data.HashFunction kitaplığındaki daha hızlı şifrelemesiz karmalardan birini seçebilirsiniz . popüler bir seçimdir.SpookyHash

Örnek uygulama

Kaynak kodu

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

Kaynak, biri 8000 bayt veya daha küçük girişler için ve bir LOB sürümü için iki işlev sunar. LOB olmayan sürüm önemli ölçüde daha hızlı olmalıdır.

Bir LOB ikilisini COMPRESS8000 bayt limitinin altına almak için içine sarmanız mümkün olabilir , eğer bu performans için faydalı olur. Alternatif olarak, LOB'yi 8000 baytlık alt segmentlere ayırabilir veya HASHBYTESLOB kasası için kullanabilirsiniz (daha uzun girdiler daha iyi ölçeklendiğinden).

Önceden oluşturulmuş kod

Açıkça kendiniz için paketi alıp her şeyi derleyebilirsiniz, ancak hızlı testi kolaylaştırmak için aşağıdaki montajları yaptım:

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

T-SQL fonksiyonları

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

kullanım

Söz konusu örnek verileri verilen örnek bir kullanım:

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 sürümünü kullanırken, ilk parametre atılmalı veya dönüştürülmelidir varbinary(max).

Yürütme planı

plan


Güvenli Spooky

Data.HashFunction kütüphanesi olarak kabul edilir CLR dil özellikleri bir dizi kullanan UNSAFEbir SQL Server tarafından. SAFEDurumu ile uyumlu bir temel Spooky Hash yazmak mümkündür . Jon Hanna'nın SpookilySharp'ı üzerine yazdığım bir örnek :

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


16

Paralelliğin SQLCLR ile herhangi bir / önemli ölçüde daha iyi olup olmayacağından emin değilim. Ancak, Util_HashBinary adlı SQL # SQLCLR kütüphanesinin (yazdığım) Ücretsiz sürümünde bir karma işlevi olduğundan test etmek gerçekten kolaydır . Desteklenen algoritmalar: MD5, SHA1, SHA256, SHA384 ve SHA512.

VARBINARY(MAX)Girdi olarak bir değer alır , böylece her alanın dize sürümünü birleştirebilir (şu anda yaptığınız gibi) ve sonra dönüştürebilir VARBINARY(MAX)veya doğrudan VARBINARYher sütun için gidebilir ve dönüştürülen değerleri birleştirebilirsiniz (bu daha hızlı olabilir. dizelerle veya dizgeden dize dönüştürmeye fazladan başlamıyorsunuzdur VARBINARY). Aşağıda bu seçeneklerin her ikisini de gösteren bir örnek verilmiştir. Ayrıca, HASHBYTESişlevi gösterir, böylece değerlerin kendisiyle SQL # .Util_HashBinary arasında aynı olduğunu görebilirsiniz .

VARBINARYDeğerleri birleştirirken karma sonuçların, NVARCHARdeğerleri birleştirirken karma sonuçlarla eşleşmeyeceğini lütfen unutmayın . Bunun nedeni, INT"1" değerinin ikili biçiminin 0x00000001 olması, UTF-16LE (yani NVARCHAR) INT"1" değerinin biçiminin ( bir karma işlevin çalışacağından ikili biçimde) 0x3100 olmasıdır.

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;

Aşağıdakileri kullanarak LOB Spooky ile daha karşılaştırılabilir bir şeyi test edebilirsiniz:

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

Not: Util_HashBinary , .NET'te yerleşik olan yönetilen SHA256 algoritmasını kullanır ve "bcrypt" kütüphanesini kullanmamalıdır.

Sorunun bu yönünün ötesinde, bu sürece yardımcı olabilecek bazı ek düşünceler var:

Ek Düşünce # 1 (hash öncesi, en az bir miktar hesaplama öncesi)

Birkaç şeyden bahsettin:

  1. Verilerin en son yüklenmesinden bu yana sütunlardan herhangi birinin gerçekten değişip değişmediğini anlamak için satırları raporlama veritabanına göre karşılaştırırız.

    ve:

  2. Raporlama tablosu için karma değerinden tasarruf edemiyorum. Tetikleyicileri veya hesaplanan sütunları desteklemeyen bir CCI

    ve:

  3. Tablolar ETL süreci dışında güncellenebilir

Bu raporlama tablosundaki veriler belirli bir süre boyunca kararlıdır ve yalnızca bu ETL işlemi ile değiştirilebilir.

Eğer bu tabloyu başka hiçbir şey değiştirmezse, o zaman gerçekten bir tetikleyici veya indekslenmiş görünüme ihtiyacımız olmaz (başlangıçta olabileceğini düşündüm).

Raporlama tablosunun şemasını değiştiremediğiniz için, en azından önceden hesaplanmış hash (ve hesaplandığında UTC zamanını) içerecek şekilde ilgili bir tablo oluşturmak mümkün müdür? Bu, bir dahaki sefere kıyasla karşılaştırmak için önceden hesaplanmış bir değere sahip olmanıza izin verir ve yalnızca karma değerini hesaplamak için gelen değeri bırakır. Bu, çağrı sayısını ya HASHBYTESda SQL#.Util_HashBinaryyarıya indirir . İçe aktarma işlemi sırasında bu karma tabloya katılabilirsiniz.

Ayrıca, bu tablonun karma değerlerini basitçe tazeleyen ayrı bir saklı yordam oluşturacaksınız. Yalnızca geçerli olacak şekilde değiştirilmiş olan ilgili satırın karma değerlerini günceller ve değiştirilen satırların zaman damgasını günceller. Bu işlem, bu tabloyu güncelleyen herhangi bir işlemin sonunda gerçekleştirilebilir / yürütülmelidir. Ayrıca, bu ETL başlangıcından 30 - 60 dakika önce (bu işlemin ne kadar süreceği ve diğer işlemlerin ne zaman yapılacağına bağlı olarak) çalışması planlanabilir. Senkronize olmayan satırlar olabileceğinden şüpheleniyorsanız, manuel olarak bile gerçekleştirilebilir.

Sonra not edildi:

500'den fazla masa var

Birçok tablonun mevcut hashları içermek için her biri için ekstra bir tablonun olmasını zorlaştırdığı, ancak standart bir şema olacağı için kodlanabileceği için bu mümkün değildir. Komut dosyası kaynak tablo adını ve kaynak tablo PK sütun (larını) keşfi için sadece hesap gerekir.

Yine de, hangi karma algoritmanın sonuçta en fazla ölçeklenebilir olduğunu kanıtlamasından bağımsız olarak, hala en azından birkaç tablo bulmayı (belki de 500 tablonun geri kalanından çok daha büyük bir kısmı vardır) ve yakalamak için ilgili bir tablo kurmayı şiddetle tavsiye ederim. akım karmaları böylece "akım" değerleri ETL işleminden önce bilinebilir. En hızlı işlev bile, hiçbir zaman ilk etapta onu çağırmak zorunda kalmaz ;-).

Ek Düşünce # 2 ( VARBINARYyerine NVARCHAR)

SQLCLR'ye karşı yerleşik HASHBYTESolmasına rağmen, daha hızlı VARBINARYolması gerektiği gibi doğrudan dönüştürmeyi tavsiye ederim . Dizeleri birleştirmek çok az verimli değil. Ve bu , dize olmayan değerleri ilk etapta dizgelere dönüştürmenin yanı sıra, ekstra çaba gerektirir (çaba miktarının temel türüne göre değişiklik gösterdiğini tahmin ediyorum: DATETIMEdaha fazlasını gerektirir BIGINT), oysa VARBINARYbasitçe size dönüşümün altında yatan değeri verir. (çoğu durumda).

Ve aslında, kullanılan ve kullanılan diğer testlerin aynı veri setini test etmek, HASHBYTES(N'SHA2_256',...)bir dakikada hesaplanan toplam karma değerlerde% 23.415 artış gösterdi. Ve artışı kullanmaktan daha hiçbir şey yapmadan için olduğunu VARBINARYyerine NVARCHAR! Details (lütfen ayrıntılar için topluluk wiki cevaplarına bakınız)

Ek Düşünce # 3 (giriş parametrelerine dikkat edin)

Diğer testler, performansı etkileyen bir alanın (bu uygulama hacmi üzerinde) girdi parametreleri olduğunu göstermiştir: kaç tane ve ne tür (ler).

Util_HashBinary : biri benim SQL # kütüphanede şu anda SQLCLR işlevi, iki giriş parametresi vardır VARBINARY(hash değeri) ve bir tane NVARCHAR(kullanımına algoritma). Bu, HASHBYTESfonksiyonun imzasını yansıtmamdan kaynaklanıyor . Ancak, eğer NVARCHARparametreyi çıkardıysam ve sadece SHA256'yı yapan bir işlev yarattıysam, performansın oldukça iyi bir şekilde geliştiğini gördüm . NVARCHARParametreyi değiştirmenin bile INTyardımcı olacağını farz ediyorum, fakat ayrıca ekstra INTparametreye sahip olmamanın bile en azından biraz daha hızlı olduğunu varsayıyorum .

Ayrıca, SqlBytes.Valuedaha iyi performans gösterebilir SqlBinary.Value.

İki yeni fonksiyon oluşturdum: Bu test için Util_HashSHA256Binary ve Util_HashSHA256Binary8k . Bunlar bir sonraki SQL # sürümüne dahil edilecektir (bunun için henüz bir tarih belirlenmemiş).

Ayrıca test metodolojisinin biraz geliştirilebileceğini öğrendim, bu nedenle aşağıdaki topluluk wiki yanıtındaki test kablo demetini aşağıdakileri içerecek şekilde güncelledim:

  1. SQLCLR düzeneklerinin ön yüklemesi, yükleme süresinin ek yükün sonuçlara çarpmamasını sağlamak için yapılır.
  2. çarpışmaları kontrol etmek için bir doğrulama prosedürü. Herhangi bir bulunursa, benzersiz / farklı satır sayısını ve toplam satır sayısını görüntüler. Bu, çarpışma sayısının (varsa) verilen kullanım durumunun sınırlarının ötesinde olup olmadığını belirlemeye izin verir. Bazı kullanım durumları az sayıda çarpışmaya izin verebilir, bazıları da gerektirmeyebilir. Süper hızlı bir işlev, istenen doğruluk düzeyindeki değişiklikleri algılayamazsa işe yaramaz. Örneğin, OP tarafından sağlanan test kablo demetini kullanarak, sıra sayısını 100k sıralara yükselttim (başlangıçta 10k idi) ve CHECKSUM% 9'luk (yikes) 9k çarpışma üzerinde kayıtlı olduğunu tespit ettim .

Ek Düşünce # 4 ( HASHBYTESbirlikte + SQLCLR?)

Darboğazın nerede olduğuna bağlı olarak HASHBYTES, aynı karıĢı yapmak için yerleşik ve bir SQLCLR UDF kombinasyonunun kullanılması bile yardımcı olabilir . Yerleşik işlevler SQLCLR işlemlerinden farklı olarak / ayrı olarak kısıtlanırsa, bu yaklaşım HASHBYTEStek tek SQLCLR'den veya SQLCLR'den daha fazla eşzamanlı olarak gerçekleştirilebilir . Kesinlikle test etmeye değer.

Ek Düşünce # 5 (nesne önbellekleme hash?)

Karma algoritma nesnesinin David Browne'ın cevabında önerildiği gibi önbelleğe alınması kesinlikle ilginç görünüyor, bu yüzden denedim ve aşağıdaki iki ilgi noktasını buldum:

  1. Sebep ne olursa olsun, eğer varsa performans artışı pek görünmüyor. Yanlış bir şey yapabilirdim, ama denedim:

    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. ManagedThreadIdDeğer, belirli bir sorgu tüm SQLCLR referanslar için aynı olduğu görülmektedir. Aynı işleve yapılan birden fazla referansı ve farklı bir işleve yapılan referansı test ettim, hepsine 3 farklı giriş değerleri verildi ve farklı (ancak beklenen) dönüş değerleri döndürdüm. Her iki test işlevi için, çıktı, ManagedThreadIdhash sonucunun dize olarak temsil edilmesinin yanı sıra onu içeren bir dizedir. ManagedThreadIdDeğer sorguda tüm UDF referanslar için aynıydı ve tüm satırlar arasında. Ancak, karma sonuç aynı giriş dizesi için aynıydı ve farklı giriş dizeleri için farklıydı.

    Testlerimde herhangi bir hatalı sonuç göremesem de, bu bir yarış durumu şansını arttırmaz mı? Sözlüğün anahtarı, belirli bir sorguda çağrılan tüm SQLCLR nesneleri için aynıysa, o anahtar için depolanan aynı değeri veya nesneyi paylaşıyorlardı, değil mi? Mesele şu ki, burada işe yaradığını sanıyordu (bir dereceye kadar, yine de çok fazla performans kazanımı görünmüyordu, ancak işlevsel olarak hiçbir şey kırılmadı), bu yaklaşımın diğer senaryolarda işe yarayacağına dair bana güven vermiyor.


11

Bu geleneksel bir cevap değil, ancak şu ana kadar sözü edilen tekniklerin bazılarının ölçütlerini göndermenin faydalı olacağını düşündüm. SQL Server 2017 CU9 ile 96 çekirdekli bir sunucu üzerinde test ediyorum.

Birçok ölçeklenebilirlik problemi, bazı küresel durumlar üzerinde rekabet eden eşzamanlı iş parçacıklarından kaynaklanmaktadır. Örneğin, klasik PFS sayfa çekişmesini düşünün. Bu, çok sayıda çalışan iş parçacığının bellekteki aynı sayfayı değiştirmesi gerektiğinde gerçekleşebilir. Kod daha etkin hale geldiğinde mandalı daha hızlı isteyebilir. Bu çekişme artar. Basitçe söylemek gerekirse, küresel devletin daha ciddi bir şekilde ele alındığı için verimli kodun ölçeklenebilirlik sorunlarına yol açma olasılığı daha yüksektir. Yavaş kodun, küresel duruma bu kadar sık ​​erişilmediğinden ölçeklenebilirlik sorunlarına neden olma olasılığı daha düşüktür.

HASHBYTESölçeklenebilirlik kısmen girdi dizesinin uzunluğuna dayanır. Benim teorim, bunun meydana gelmesinin nedeni, HASHBYTESfonksiyon çağrıldığında bazı küresel duruma erişimin gerekli olduğuydu. Gözleminin kolay olması gereken genel durum, SQL Server'ın bazı sürümlerinde çağrı başına tahsis edilmesi gereken bir bellek sayfasıdır. Dikkat edilmesi zor olan, bir tür işletim sistemi çekişmesi olduğudur. Sonuç olarak, HASHBYTESdaha az sıklıkta kod tarafından çağrılırsa çekişme azalır. HASHBYTESÇağrı oranını azaltmanın bir yolu, çağrı başına ihtiyaç duyulan karma iş sayısını artırmaktır. Hashing çalışması kısmen giriş dizesinin uzunluğuna dayanır. Uygulamada gördüğüm ölçeklenebilirlik problemini yeniden oluşturmak için demo verilerini değiştirmem gerekiyordu. Makul bir en kötü durum senaryosu 21 olan bir tablodur.BIGINTsütunlar. Tablonun tanımı alttaki kodda yer almaktadır. Local Factors ™ 'ı azaltmak için MAXDOP 1, nispeten küçük masalarda çalışan eşzamanlı sorgular kullanıyorum . Hızlı referans kodum en altta.

İşlevlerin farklı karma uzunlukları döndürdüğünü unutmayın. MD5ve SpookyHashher ikisi de 128 bitlik karmadır, SHA256256 bitlik karmadır.

(Sonuçlar NVARCHARvs VARBINARYdönüşüm ve birleştirme)

Dönüştürme ve birleştirme VARBINARYişleminin gerçekten daha verimli / performans gösterip göstermediğini görmek için, aynı şablondan depolanan prosedürün NVARCHARbir NVARCHARversiyonunun RUN_HASHBYTES_SHA2_256yaratıldığından daha etkili / performans gösterdiğini ( aşağıdaki BENCHMARKING CODE bölümündeki "5. Adım" a bakınız). Tek farklar:

  1. Saklı yordam adı bitiyor _NVC
  2. BINARY(8)için CASTişlev için değiştirildiNVARCHAR(15)
  3. 0x7C olarak değiştirildi N'|'

Sonuçlanan:

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

yerine:

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

Aşağıdaki tablo 1 dakika içinde gerçekleştirilen karma sayısını içerir. Testler, aşağıda belirtilen diğer testler için kullanılanlardan farklı bir sunucuda gerçekleştirildi.

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

Sadece ortalamalara bakarak, şuna geçiş yapmanın yararını hesaplayabiliriz VARBINARY:

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

Bu döner:

IncreaseAmount:    2400000.0
IncreasePercentage:   23.415

SONUÇLAR (karma algoritmalar ve uygulamalar)

Aşağıdaki tablo 1 dakika içinde gerçekleştirilen karma sayısını içerir. Örneğin, CHECKSUM84 eşzamanlı sorgu ile kullanma , zaman dolmadan 2 milyardan fazla karma gerçekleştirildi.

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

İkinci saniyede iş açısından ölçülen aynı sayıları görmeyi tercih ederseniz:

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

Tüm yöntemler hakkında bazı hızlı düşünceler:

  • CHECKSUM: beklendiği gibi çok iyi ölçeklenebilirlik
  • HASHBYTES: ölçeklenebilirlik sorunları, arama başına bir bellek ayırma ve işletim sisteminde harcanan büyük miktarda CPU içerir.
  • Spooky: şaşırtıcı derecede iyi ölçeklenebilirlik
  • Spooky LOB: çevirme kilidi SOS_SELIST_SIZED_SLOCKkontrolden çıkar. Bunun LOB'leri CLR işlevlerinden geçirmede genel bir sorun olduğunu düşünüyorum, ancak emin değilim
  • Util_HashBinary: aynı spinlock tarafından vurulmuş gibi görünüyor. Şu ana kadar bu konuya bakmadım çünkü muhtemelen bu konuda yapabileceğim çok fazla şey yok:

kilidini çevir

  • Util_HashBinary 8k: çok şaşırtıcı sonuçlar, burada neler olduğundan emin değilsiniz

Son sonuçlar daha küçük bir sunucuda test edilmiştir:

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

KİMLİK KODU

KURULUM 1: Tablolar ve Veriler

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

KURULUM 2: Master Yürütme Proc

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

KURULUM 3: Çarpışma Algılama 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

KURULUM 4: Temizleme (DROP Tüm Test İşlemleri)

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

KURULUM 5: Test İşlemleri Oluştur

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

TEST 1: Çarpışmaları Kontrol Edin

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;

TEST 2: Performans Testlerini Çalıştırın

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;

ÇÖZÜLMESİ GEREKEN KONULAR

Bir tekil SQLCLR UDF'nin performans testine odaklanırken, daha önce tartışılan iki konu testlere dahil edilmedi, ancak hangi yaklaşımın tüm gereklilikleri karşıladığını belirlemek için ideal olarak araştırılmalıdır .

  1. İşlev her sorgu için iki kez yürütülür (içe aktarma satırı için bir kez ve geçerli satır için bir kez). Şimdiye kadar yapılan testler, UDF'ye yalnızca bir kez test sorgularında başvuruda bulundu. Bu faktör, seçeneklerin sıralamasını değiştirmeyebilir, ancak tam olması durumunda göz ardı edilmemesi gerekir.
  2. O zamandan beri silinen bir yorumda Paul White şunları söyledi:

    Bir HASHBYTESCLR skaler işleviyle değiştirmenin bir dezavantajı - CLR işlevlerinin toplu modu kullanamayacağı gibi görünüyor HASHBYTES. Bu, performans açısından önemli olabilir.

    Bu da dikkate alınması gereken bir şey ve açıkça test gerektiriyor. Eğer SQLCLR seçenekleri yerleşik kullanım üzerinde herhangi bir fayda sağlamazsa HASHBYTES, bu durum Süleyman'ın mevcut hasheleri yakalama önerisine (en azından en büyük tablolar için) ilgili tablolara ağırlık katar .


6

İşlev çağrısında oluşturulan tüm nesneleri havuzlayarak ve önbellekleyerek, performansı ve belki de tüm .NET yaklaşımlarının ölçeklenebilirliğini artırabilirsiniz. Paul White'ın yukarıdaki kodu için EG:

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, statik / paylaşılan değişkenlerin kullanılmasını engeller ve önlemeye çalışır, ancak bunları salt okunur olarak işaretlerseniz paylaşılan değişkenleri kullanmanıza izin verir. Tabii ki, ki gibi sadece bazı değişken türlerden tek bir örnek atayabileceğiniz gibi anlamsız ConcurrentDictionary.


ilginç ... eğer aynı örneği tekrar tekrar kullanıyorsa, bu konu güvenli midir? Yönetilen karmaların bir Clear()metodu olduğunu biliyorum ama o kadar Spooky'a bakmadım.
Süleyman Rutzky

@ PaulWhite ve David. Yanlış bir şey yapabilirdim ya da SHA256Managedve arasında bir fark olabilirdi SpookyHashV2, ama bunu denedim ve eğer varsa performans artışı görmedim. Ayrıca, ManagedThreadIddeğerin belirli bir sorgudaki tüm SQLCLR referansları için aynı olduğunu fark ettim . Aynı işleve yapılan birden fazla referansı ve farklı bir işleve yapılan referansı test ettim, hepsine 3 farklı giriş değerleri verildi ve farklı (ancak beklenen) dönüş değerleri döndürdüm. Bu bir yarış durumu şansını arttırmaz mıydı? Adil olmak gerekirse, sınavımda hiç görmedim.
Solomon Rutzky,
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.