SQL CLRスカラー関数を使用してHASHBYTESをシミュレートするスケーラブルな方法は何ですか?


29

ETLプロセスの一環として、ステージングからの行をレポートデータベースと比較して、データが最後に読み込まれてから実際に変更された列があるかどうかを確認します。

この比較は、テーブルの一意のキーと、他のすべての列のある種のハッシュに基づいています。現在HASHBYTES、このSHA2_256アルゴリズムで使用しており、多数の並行ワーカースレッドがすべて呼び出している場合、大規模サーバーではスケーリングしないことがわかりましたHASHBYTES

96コアサーバーでテストする場合、1秒あたりのハッシュで測定されるスループットは、16を超える同時スレッドを増加させません。同時MAXDOP 8クエリの数を1〜12に変更してテストします。テストでMAXDOP 1は、同じスケーラビリティのボトルネックが示されました。

回避策として、SQL CLRソリューションを試したいと思います。要件を述べるための私の試みは次のとおりです。

  • 関数は並列クエリに参加できる必要があります
  • 関数は決定的でなければなりません
  • この関数は、NVARCHARまたはVARBINARY文字列の入力を受け取る必要があります(関連するすべての列は連結されます)
  • 文字列の一般的な入力サイズは、長さが100〜20000文字です。20000は最大値ではありません
  • ハッシュ衝突の可能性は、MD5アルゴリズムとほぼ同等かそれ以上でなければなりません。CHECKSUM衝突が多すぎるため、機能しません。
  • この機能は、大規模なサーバーで適切にスケーリングする必要があります(スレッド数が増加しても、スレッドあたりのスループットが大幅に低下することはありません)

Application Reasons™の場合、レポートテーブルのハッシュの値を保存できないと仮定します。これは、トリガーまたは計算列をサポートしないCCIです(他の問題もありますが、これには入りたくありません)。

HASHBYTESSQL 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

変更を探しているだけなので、暗号化ハッシュ関数は必要ありません。

Brandon DahlerのオープンソースData.HashFunctionライブラリにある高速で暗号化されていないハッシュのいずれかから選択できます。これは、寛容でOSIが承認したMITライセンスの下でライセンスされています。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バージョン用の2つの関数を提供します。非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)
    );

LOBバージョンを使用する場合、最初のパラメーターをキャストまたは変換する必要があります varbinary(max)

実行計画

計画


安全な不気味な

Data.HashFunctionのライブラリが考慮されるCLR言語機能の数使用するUNSAFESQL Serverでの。SAFEステータスと互換性のある基本的なSpooky Hashを作成することができます。ジョン・ハンナのSpookilySharpに基づいて書いた例以下に示します。

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


16

SQLCLRを使用した場合に、並列処理が改善されるかどうかはわかりません。ただし、Util_HashBinaryと呼ばれるSQL# SQLCLRライブラリのFreeバージョン(私が書いた)にはハッシュ関数があるため、テストは非常に簡単です。サポートされるアルゴリズムは、MD5、SHA1、SHA256、SHA384、およびSHA512です。

VARBINARY(MAX)入力として値を取るため、各フィールドの文字列バージョンを連結して(現在のように)に変換するVARBINARY(MAX)VARBINARY、各列に直接移動して変換された値を連結することができます(これは、文字列または文字列からVARBINARY)への余分な変換を処理していません。以下は、これらのオプションの両方を示す例です。また、HASHBYTES関数とSQL#.Util_HashBinaryの間で値が同じであることを確認できるように関数を示しています。

VARBINARY値を連結した場合のハッシュ結果は、値を連結した場合のハッシュ結果と一致しないことに注意してくださいNVARCHAR。これは、INT値「1」のバイナリ形式が0x00000001であるのに対し、「1」の値のUTF-16LE(つまりNVARCHAR)形式INT(ハッシュ関数が動作するため、バイナリ形式)が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は、.NETに組み込まれているマネージSHA256アルゴリズムを使用します。「bcrypt」ライブラリを使用しないでください。

質問のその側面を超えて、このプロセスを助けるかもしれないいくつかの追加の考えがあります:

追加の考え#1(事前計算ハッシュ、少なくともいくつか)

あなたはいくつかのことを言及しました:

  1. ステージングからの行をレポートデータベースと比較して、データが最後に読み込まれてから実際に変更された列があるかどうかを判断します。

    そして:

  2. レポートテーブルのハッシュの値を保存できません。これは、トリガーまたは計算列をサポートしないCCIです

    そして:

  3. テーブルはETLプロセスの外部で更新できます

このレポートテーブルのデータは一定期間安定しており、このETLプロセスによってのみ変更されるようです。

このテーブルを変更するものが他にない場合、トリガーやインデックス付きビューは本当に必要ありません(最初はそうするかもしれないと思っていました)。

レポートテーブルのスキーマは変更できないため、少なくとも、事前に計算されたハッシュ(および計算されたUTC時間)を含む関連テーブルを作成することは可能でしょうか?これにより、事前に計算された値を次回と比較して、ハッシュの計算を必要とする着信値のみを残すことができます。これにより、どちらかHASHBYTESまたはSQL#.Util_HashBinary半分への呼び出し回数が減少します。インポートプロセス中に、このハッシュテーブルに単純に参加します。

また、このテーブルのハッシュを単純に更新する別個のストアドプロシージャを作成します。変更された関連行のハッシュを最新に更新し、変更された行のタイムスタンプを更新するだけです。このプロシージャは、このテーブルを更新する他のプロセスの終了時に実行できます/実行する必要があります。また、このETLが開始する30〜60分前に実行するようにスケジュールすることもできます(実行にかかる時間、およびこれらの他のプロセスが実行されるタイミングによって異なります)。同期していない行があると思われる場合は、手動で実行することもできます。

その後、次のことに留意されました。

500以上のテーブルがあります

多くのテーブルがあるため、現在のハッシュを格納するために各テーブルごとに追加のテーブルを作成することは難しくなりますが、これは標準スキーマであるためスクリプト化できるため不可能ではありません。スクリプトは、ソーステーブル名とソーステーブルPK列の検出を考慮するだけで済みます。

それでも、最終的にどのハッシュアルゴリズムが最もスケーラブルであることが証明されても、少なくともいくつかのテーブル(おそらく500個のテーブルの残りよりもはるかに大きいものがある)を見つけて、キャプチャする関連テーブルを設定することを強くお勧めしますETLプロセスの前に「現在の」値を知ることができるように、現在のハッシュ。最速の関数であっても、そもそも呼び出す必要はありません;-)。

追加の考え#2(のVARBINARY代わりにNVARCHAR

かかわらず、SQLCLRのビルトイン対HASHBYTES、私はまだ直接変換推薦するVARBINARYことのようにする必要があり速くなります。文字列の連結はひどく効率的ではありません。そして、余分な労力を必要とする最初の場所に文字列に文字列以外の値の変換に加えて、その者は、(私は努力の量がベースタイプによって異なりますと仮定します。DATETIME以上必要BIGINT)、に変換するのに対し、VARBINARY単にあなたの根本的な価値を提供します(ほとんどの場合)。

実際、他のテストと同じデータセットを使用してテストしHASHBYTES(N'SHA2_256',...)たところ、1分間に計算された合計ハッシュが23.415%増加しました。そして、その増加は、より多く使用するよりも何もしないためだったVARBINARYの代わりにNVARCHAR!😸(詳細については、コミュニティWikiの回答をご覧ください)

追加の考え#3(入力パラメーターに注意してください)

さらにテストを行ったところ、(この実行量を超える)パフォーマンスに影響を与える1つの領域は、入力パラメーターであることがわかりました:数と種類。

Util_HashBinary 1:私のSQLの#ライブラリに現在あるSQLCLR関数は、2つの入力パラメータがあるVARBINARY(ハッシュに値)、および1 NVARCHAR(使用するアルゴリズム)。これは、HASHBYTES関数の署名をミラーリングするためです。ただし、NVARCHARパラメーターを削除し、SHA256のみを実行する関数を作成すると、パフォーマンスが非常に良く向上することがわかりました。NVARCHARパラメータを切り替えることでもINT助けになったと思いINTますが、余分なパラメータを持たなくても少なくとも少し速いと思います。

また、のSqlBytes.Valueパフォーマンスが向上する場合がありSqlBinary.Valueます。

このテスト用に、Util_HashSHA256BinaryUtil_HashSHA256Binary8kの 2つの新しい関数を作成しました。これらは、SQL#の次のリリースに含まれます(その日付はまだ設定されていません)。

また、テストの方法論がわずかに改善される可能性があることもわかったため、以下のコミュニティWikiの回答のテストハーネスを更新しました。

  1. ロード時間のオーバーヘッドによって結果が歪まないようにするためのSQLCLRアセンブリのプリロード。
  2. 衝突をチェックする検証手順。見つかった場合は、一意/個別の行の数と行の総数が表示されます。これにより、衝突の数(存在する場合)が特定のユースケースの制限を超えているかどうかを判断できます。いくつかのユースケースでは、少数の衝突が許可される場合がありますが、他のユースケースでは衝突が不要な場合もあります。目的の精度レベルへの変更を検出できない場合、超高速機能は役に立ちません。たとえば、OPが提供するテストハーネスを使用して、行数を100k行(元は10k)に増やし、CHECKSUM9k回の衝突(9%(yikes))を記録することがわかりました。

追加の考え#4(HASHBYTES+ SQLCLRを一緒に?)

ボトルネックがどこにあるかによっては、ビルトインHASHBYTESとSQLCLR UDFの組み合わせを使用して同じハッシュを実行することも役立ちます。組み込み関数がSQLCLR操作とは異なる/別々に制約されている場合、このアプローチはHASHBYTES、SQLCLRのいずれかまたはSQLCLRを個別に同時に実行できる場合があります。それは間違いなくテストする価値があります。

追加の考え#5(オブジェクトキャッシュのハッシュ?)

David Browneの答えで示唆されたハッシュアルゴリズムオブジェクトのキャッシュは確かに興味深いように思えたので、試してみて、次の2つの興味深い点を見つけました。

  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

これは伝統的な答えではありませんが、これまでに述べたテクニックのいくつかのベンチマークを投稿することは役立つと思いました。SQL Server 2017 CU9を搭載した96コアサーバーでテストしています。

多くのスケーラビリティの問題は、グローバルステートを巡って競合する同時スレッドが原因です。たとえば、従来のPFSページの競合を検討してください。これは、メモリ内の同じページを変更する必要があるワーカースレッドが多すぎる場合に発生する可能性があります。コードがより効率的になると、ラッチをより速く要求できます。それは競合を増やします。簡単に言うと、効率的なコードはスケーラビリティの問題につながる可能性が高くなります。これは、グローバル状態がより厳しく争われるためです。グローバル状態はそれほど頻繁にアクセスされないため、遅いコードはスケーラビリティの問題を引き起こす可能性が低くなります。

HASHBYTESスケーラビリティは、入力文字列の長さに部分的に基づいています。私の理論では、これが発生する理由は、HASHBYTES関数が呼び出されたときにグローバル状態へのアクセスが必要だということでした。観察しやすいグローバル状態は、SQL Serverの一部のバージョンで呼び出しごとにメモリページを割り当てる必要があることです。観察するのが難しいのは、ある種のOSの競合があることです。その結果、HASHBYTESコードによって呼び出される頻度が低くなると、競合がなくなります。列の割合を減らす1つの方法。テーブルの定義は、下部のコードに含まれています。Local Factors™を削減するために、比較的小さなテーブルで動作する同時クエリを使用しています。私のクイックベンチマークコードは一番下にあります。HASHBYTES呼び出しは呼び出しごとに必要なハッシュ処理の量を増やすことです。ハッシュ処理は、部分的に入力文字列の長さに基づいています。アプリケーションで見たスケーラビリティの問題を再現するには、デモデータを変更する必要がありました。合理的な最悪のシナリオは、21の表ですBIGINTMAXDOP 1

関数は異なるハッシュ長を返すことに注意してください。MD5そしてSpookyHash、両方の128ビットハッシュであり、SHA256256ビットのハッシュです。

結果(NVARCHARvs VARBINARY変換および連結)

変換、及び連結かどうかを確認するために、VARBINARYより真のより効率的な/のパフォーマンスでありNVARCHARNVARCHARバージョンのRUN_HASHBYTES_SHA2_256ストアドプロシージャが同じテンプレートから作成された(中「ステップ5」を参照してベンチマーキングCODEの下部分)。唯一の違いは次のとおりです。

  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の同時クエリを使用すると、タイムアウトになる前に20億を超えるハッシュが実行されました。

╔════════════════════╦════════════╦════════════╦════════════╗
      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:スケーラビリティの問題には、呼び出しごとに1つのメモリ割り当てとOSで消費される大量のCPUが含まれます
  • Spooky:驚くほど優れたスケーラビリティ
  • Spooky LOB:スピンロックSOS_SELIST_SIZED_SLOCKは制御不能になります。これはCLR関数を介してLOBを渡す際の一般的な問題であると思われますが、よくわかりません
  • 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
);

セットアップ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:衝突検出プロセス

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:クリーンアップ(すべてのテストプロセスを削除)

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のパフォーマンステストに焦点を当てながら、初期に議論された2つの問題はテストに組み込まれていませんが、理想的には、どのアプローチがすべての要件を満たすかを判断するために調査する必要があります。

  1. この関数は、クエリごとに2回実行されます(インポート行に対して1回、現在の行に対して1回)。これまでのテストでは、テストクエリでUDFを1回だけ参照していました。この要素はオプションのランキングを変更しないかもしれませんが、念のため無視してはいけません。
  2. その後削除されたコメントで、ポール・ホワイトは次のように言及していました。

    HASHBYTESCLRスカラー関数に置き換えることの1つの欠点-CLR関数はバッチモードを使用できないのに対し、バッチモードを使用できないようHASHBYTESです。それはパフォーマンス面で重要かもしれません。

    したがって、それは考慮すべきことであり、明らかにテストが必要です。SQLCLRオプションがビルトインよりも利点を提供しない場合、既存のハッシュ(少なくとも最大のテーブル)を関連テーブルにキャプチャするというSolomonの提案HASHBYTES重みが追加されます。


6

関数呼び出しで作成されたオブジェクトをプールおよびキャッシュすることにより、おそらくパフォーマンスを向上させることができ、おそらくすべての.NETアプローチのスケーラビリティを向上させることができます。上記の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を詳しく調べたことはありません。
ソロモンラッツキー

@PaulWhiteとDavid。私が行わ何かを間違っていたかもしれない、またはそれは違い可能性SHA256ManagedSpookyHashV2、私はこれを試してみましたが、もしあれば、パフォーマンスの向上をあまり見ませんでした。またManagedThreadId、特定のクエリのすべてのSQLCLR参照の値が同じであることに気付きました。同じ関数への複数の参照、および異なる関数への参照をテストしました。3つすべてに異なる入力値が与えられ、異なる(ただし予期される)戻り値が返されます。これにより、競合状態の可能性が増加しませんか?公平を期すために、私のテストでは何も見ませんでした。
ソロモンラッツキー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.