ETLプロセスの一部として、ステージングからの行をレポートデータベースと比較して、データが最後にロードされてから実際に変更されている列があるかどうかを調べます。
比較は、テーブルの一意のキーと他のすべての列のある種のハッシュに基づいています。現在 HASHBYTES
をSHA2_256
アルゴリズムとともに使用しており、多数の同時ワーカースレッドがすべてHASHBYTES
を呼び出している場合、大規模サーバーではスケーリングしないことがわかりました。
1秒あたりのハッシュで測定されたスループットは、96コアサーバーでテストした場合、16を超える同時スレッドを増加させません。同時MAXDOP 8
クエリの数を1〜12に変更してテストします。MAXDOP 1
を使用したテストでは、同じスケーラビリティのボトルネックが示されました。
回避策として、SQL CLRソリューションを試してみたいと思います。要件を述べる私の試みはここにあります:
NVARCHAR
またはVARBINARY
文字列の入力を受け取る必要があります(関連するすべての列が連結されます)CHECKSUM
は衝突が多すぎるため機能しません。Application Reasons™の場合、レポートテーブルのハッシュの値を節約できないと仮定します。これは、トリガーまたは計算列をサポートしないCCIです(他の問題もあり、私が知りたくない)。
SQL CLR関数を使用してHASHBYTES
をシミュレートするスケーラブルな方法は何ですか?私の目標は、大規模サーバーで可能な限り1秒あたりのハッシュを取得することで表現できるため、パフォーマンスも重要です。私は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);
変更を探すだけなので、暗号化ハッシュ関数は必要ありません。
オープンソースのより高速な非暗号化ハッシュの1つから選択できます Data.HashFunctionライブラリ Brandon Dahlerによるもので、許可および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;
}
}
_
ソースは2つの関数を提供します。1つは8000バイト以下の入力用で、1つはLOBバージョンです。非LOBバージョンは大幅に高速化されます。
LOBバイナリを COMPRESS
でラップして、8000バイトの制限内に収めることができる場合があります(パフォーマンスにとって価値がある場合)。または、LOBをサブ8000バイトのセグメントに分割するか、LOBの場合のためにHASHBYTES
の使用を予約することもできます(長い入力はより適切にスケーリングされるため)。
自分でパッケージを取得してすべてをコンパイルできることは明らかですが、簡単なテストを簡単にするために、以下のアセンブリを作成しました。
https://Gist.github.com/SQLKiwi/365b265b476bf86754457fc9514b23
_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ライブラリは、SQL ServerによってUNSAFE
と見なされる多数のCLR言語機能を使用します。 SAFE
ステータスと互換性のある基本的なSpooky Hashを書くことは可能です。 Jon HannaのSpookilySharp に基づいて私が書いた例は以下のとおりです。
https://Gist.github.com/SQLKiwi/7a5bb26b0bee56f6d28a1d26669ce8f2
SQLCLRを使用すると、並列処理が向上するかどうかがわかりません。ただし、無料バージョンの SQL# SQLCLRライブラリ(私が書いた)til_HashBinaryと呼ばれるハッシュ関数があるため、テストは本当に簡単です。サポートされるアルゴリズムは、MD5、SHA1、SHA256、SHA384、およびSHA512です。
これはVARBINARY(MAX)
値を入力として受け取るため、各フィールドの文字列バージョンを(現在行っているように)連結してからVARBINARY(MAX)
に変換するか、または直接各列のVARBINARY
と変換された値を連結します(文字列や文字列からVARBINARY
への追加の変換を処理していないため、これはより高速になる可能性があります)。以下は、これらのオプションの両方を示す例です。また、HASHBYTES
関数も表示されるため、値がSQL#.Util_HashBinaryと同じであることがわかります。
VARBINARY
値を連結したときのハッシュ結果は、NVARCHAR
値を連結したときのハッシュ結果と一致しないことに注意してください。これは、INT
値「1」のバイナリ形式が0x00000001であるのに対し、UTF-16LE(つまりNVARCHAR
)形式の「1」の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スプーキーにさらに匹敵するものをテストできます。
_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];
_
注:til_HashBinaryは、.NETに組み込まれているマネージドSHA256アルゴリズムを使用するため、「bcrypt」ライブラリを使用しないでください。
質問のその側面以外に、このプロセスに役立つ可能性があるいくつかの追加の考えがあります。
あなたはいくつかのことを述べました:
ステージングの行をレポートデータベースと比較して、データが最後に読み込まれてから実際に変更されている列があるかどうかを調べます。
そして:
レポートテーブルのハッシュの値を保存できません。トリガーや計算列をサポートしないCCIです。
そして:
テーブルはETLプロセスの外部で更新できます
このレポートの表のデータは一定期間安定しており、このETLプロセスによってのみ変更されるようです。
他に何もこのテーブルを変更しない場合、結局のところ、トリガーやインデックス付きビューは本当に必要ありません(私は最初はそうするかもしれないと思っていました)。
レポートテーブルのスキーマを変更できないため、少なくとも事前計算されたハッシュ(および計算されたときのUTC時間)を含む関連テーブルを作成することは可能ですか?これにより、事前に計算された値を次回と比較して、ハッシュの計算が必要な入力値のみを残すことができます。これにより、HASHBYTES
または_SQL#.Util_HashBinary
_の呼び出し回数が半分になります。インポートプロセス中に、このハッシュのテーブルに単純に結合します。
また、このテーブルのハッシュを単に更新する別のストアドプロシージャを作成します。現在に変更された関連行のハッシュを更新し、変更された行のタイムスタンプを更新するだけです。このプロシージャは、このテーブルを更新する他のプロセスの最後に実行できます。また、このETLが開始する30〜60分前に実行するようにスケジュールすることもできます(実行にかかる時間、およびこれらの他のプロセスのいずれが実行されるかによって異なります)。同期されていない行があると思われる場合は、手動で実行することもできます。
その後、次のことが指摘されました。
500以上のテーブルがあります
テーブルの数が多いと、現在のハッシュを含めるためにテーブルごとに追加のテーブルを作成することが難しくなりますが、標準のスキーマであるため、スクリプト化できるため、これは不可能ではありません。スクリプトは、ソーステーブルの名前とソーステーブルのPK列の検出を考慮するだけで済みます。
それでも、最終的に最もスケーラブルであることが判明したハッシュアルゴリズムに関係なく、私は非常に少なくともいくつかのテーブルを見つけることをお勧めします(おそらく他のテーブルよりもはるかに大きいテーブルがあるかもしれません) 500テーブル)、および現在のハッシュを取得するように関連テーブルを設定して、ETLプロセスの前に「現在の」値を把握できるようにします。最速の関数でさえ、そもそもそれを呼び出す必要がないため、パフォーマンスを上げることはできません;-)。
VARBINARY
の代わりにNVARCHAR
)SQLCLRと組み込みのHASHBYTES
に関係なく、直接VARBINARY
に変換することをお勧めしますの方が速いので。文字列を連結することは、非常に効率的ではありません。 そして、それはそもそも非文字列値を文字列に変換することに加えて、追加の作業が必要です(作業量は基本タイプに基づいて異なると思います:DATETIME
はBIGINT
よりも多く必要ですが、VARBINARY
に変換すると、基本的な値が得られます(ほとんどの場合)。
そして実際、他のテストと同じデータセットをテストし、HASHBYTES(N'SHA2_256',...)
を使用すると、1分間で計算された合計ハッシュが23.415%増加しました。そして、その増加は、VARBINARY
の代わりにNVARCHAR
を使用する以外に何もしなかったためです! ????(詳細は コミュニティwikiの回答 を参照してください)
さらなるテストにより、(この実行量で)パフォーマンスに影響を与える1つの領域が入力パラメーターであることがわかりました。
現在SQL#ライブラリにあるtil_HashBinary SQLCLR関数には2つの入力パラメーターがあります。1つはVARBINARY
(ハッシュする値)、もう1つはNVARCHAR
(アルゴリズムを使用する)。これは、HASHBYTES
関数の署名をミラーリングしたためです。ただし、NVARCHAR
パラメーターを削除し、SHA256のみを実行する関数を作成すると、パフォーマンスが非常に向上することがわかりました。 NVARCHAR
パラメータをINT
に切り替えることも役立つと思いますが、余分なINT
パラメータを使用しなくても少なくとも少し速く。
また、_SqlBytes.Value
_は_SqlBinary.Value
_よりもパフォーマンスが向上する可能性があります。
このテスト用に、2つの新しい関数til_HashSHA256Binaryおよびtil_HashSHA256Binary8kを作成しました。これらは、SQL#の次のリリースに含まれる予定です(日付はまだ設定されていません)。
また、テスト方法が少し改善される可能性があることもわかったので、以下のコミュニティWiki回答のテストハーネスを更新して、以下を含めました。
CHECKSUM
が9kを超える衝突を登録しました。これは9%(yikes)です。HASHBYTES
+ SQLCLRを一緒に?)ボトルネックの場所によっては、組み込みHASHBYTES
とSQLCLR UDFを組み合わせて同じハッシュを実行すると役立つ場合もあります。組み込み関数がSQLCLR操作とは異なる方法で個別に制約されている場合、このアプローチは、HASHBYTES
またはSQLCLRを個別に実行するよりも同時に実行できる可能性があります。間違いなくテストする価値があります。
David Browneの回答 で提案されているハッシュアルゴリズムオブジェクトのキャッシュは確かに興味深いと思われるので、試してみたところ、次の2つの重要な点が見つかりました。
何らかの理由で、パフォーマンスの改善はあったとしてもあまり提供されていないようです。私は何か間違ったことをしたかもしれませんが、これが私が試したものです:
_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);
}
_
ManagedThreadId
値は、特定のクエリのすべてのSQLCLR参照で同じであるように見えます。同じ関数への複数の参照と、異なる関数への参照をテストしました。3つすべてに異なる入力値が与えられ、異なる(ただし期待される)戻り値が返されました。両方のテスト関数の出力は、ManagedThreadId
を含む文字列と、ハッシュ結果の文字列表現でした。 ManagedThreadId
値は、クエリ内のすべてのUDF参照、およびすべての行で同じでした。しかし、ハッシュ結果は同じ入力文字列では同じであり、異なる入力文字列では異なっていました。
テストで誤った結果は見られませんでしたが、これにより競合状態の可能性が高まるのではないですか?ディクショナリのキーが特定のクエリで呼び出されるすべてのSQLCLRオブジェクトで同じである場合、それらはそのキーに格納されている同じ値またはオブジェクトを共有しているでしょう。ここで機能しているように見えても(ある程度、パフォーマンスはそれほど向上していないようですが、機能的には何も壊れていません)、このアプローチが他のシナリオで機能するという確信はありません。
これは伝統的な答えではありませんが、これまでに述べたいくつかの手法のベンチマークを投稿すると役立つと思いました。 SQL Server 2017 CU9を搭載した96コアサーバーでテストしています。
スケーラビリティの問題の多くは、いくつかのグローバルな状態をめぐって競合する並行スレッドが原因です。たとえば、従来のPFSページの競合を考えてみます。これは、メモリ内の同じページを変更する必要のあるワーカースレッドが多すぎる場合に発生する可能性があります。コードがより効率的になると、より速くラッチを要求する可能性があります。それは競合を増やします。簡単に言えば、グローバルな状態がより厳しく争われているため、効率的なコードはスケーラビリティの問題につながる可能性が高くなります。グローバル状態に頻繁にアクセスしないため、遅いコードはスケーラビリティの問題を引き起こす可能性が低くなります。
HASHBYTES
スケーラビリティは、部分的には入力文字列の長さに基づいています。私の理論では、これが発生する理由は、HASHBYTES
関数が呼び出されたときにグローバル状態へのアクセスが必要になるというものでした。観察しやすいグローバルな状態は、SQL Serverの一部のバージョンでは、呼び出しごとにメモリページを割り当てる必要があることです。観察するのが難しいのは、なんらかのOSの競合があることです。その結果、コードがHASHBYTES
を呼び出す頻度が低くなると、競合が減少します。 HASHBYTES
呼び出しの割合を減らす1つの方法は、呼び出しごとに必要なハッシュ処理の量を増やすことです。ハッシュ処理は、部分的には入力文字列の長さに基づいています。アプリケーションで見たスケーラビリティの問題を再現するには、デモデータを変更する必要がありました。合理的な最悪のシナリオは、21のBIGINT
列を持つテーブルです。テーブルの定義は下部のコードに含まれています。 Local Factors™を削減するために、比較的小さなテーブルで動作する_MAXDOP 1
_クエリを同時に使用しています。私の簡単なベンチマークコードは一番下にあります。
関数は異なるハッシュ長を返すことに注意してください。 _MD5
_とSpookyHash
はどちらも128ビットのハッシュであり、_SHA256
_は256ビットのハッシュです。
NVARCHAR
とVARBINARY
の変換および連結)VARBINARY
は、NVARCHAR
に変換して連結するかどうかを確認するために、NVARCHAR
よりも効率的でパフォーマンスが高いため、_RUN_HASHBYTES_SHA2_256
_ストアドプロシージャのバージョンが作成されました。同じテンプレートから(下記のBENCHMARKING CODEセクションの「ステップ5」を参照)。唯一の違いは次のとおりです。
_NVC
_で終わりますCAST
関数のBINARY(8)
がNVARCHAR(15)
に変更されました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分間に実行されるハッシュの数を示しています。たとえば、84個の同時クエリでCHECKSUM
を使用すると、タイムアウトになる前に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:クリーンアップ(DROP All Test Procs)
_DECLARE @SQL NVARCHAR(MAX) = N'';
SELECT @SQL += N'DROP PROCEDURE [dbo].' + QUOTENAME(sp.[name])
+ N';' + NCHAR(13) + NCHAR(10)
FROM sys.objects sp
WHERE sp.[name] LIKE N'RUN[_]%'
AND sp.[type_desc] = N'SQL_STORED_PROCEDURE'
AND sp.[name] <> N'RUN_HASHES_FOR_ONE_MINUTE'
PRINT @SQL;
EXEC (@SQL);
_
設定5:テストプロシージャの生成
_SET NOCOUNT ON;
DECLARE @TestProcsToCreate TABLE
(
ProcName sysname NOT NULL,
CodeToExec NVARCHAR(261) NOT NULL
);
DECLARE @ProcName sysname,
@CodeToExec NVARCHAR(261);
INSERT INTO @TestProcsToCreate VALUES
(N'SpookyHash', N'dbo.SpookyHash('),
(N'HASHBYTES_MD5', N'HASHBYTES(''MD5'','),
(N'HASHBYTES_SHA2_256', N'HASHBYTES(''SHA2_256'','),
(N'CHECKSUM', N'CHECKSUM('),
(N'SpookyHashLOB', N'dbo.SpookyHashLOB('),
(N'SR_MD5', N'SQL#.Util_HashBinary(N''MD5'','),
(N'SR_SHA256', N'SQL#.Util_HashBinary(N''SHA256'','),
(N'SR_MD5_8k', N'SQL#.[Util_HashBinary8k](N''MD5'','),
(N'SR_SHA256_8k', N'SQL#.[Util_HashBinary8k](N''SHA256'',')
--/* -- BETA / non-public code
, (N'SR_SHA256_new', N'SQL#.[Util_HashSHA256Binary8k]('),
(N'SR_SHA256LOB_new', N'SQL#.[Util_HashSHA256Binary](');
--*/
DECLARE @ProcTemplate NVARCHAR(MAX),
@ProcToCreate NVARCHAR(MAX);
SET @ProcTemplate = N'
CREATE OR ALTER PROCEDURE dbo.RUN_{{ProcName}}
AS
BEGIN
DECLARE @dummy INT;
SET NOCOUNT ON;
SELECT @dummy = COUNT({{CodeToExec}}
CAST(FK1 AS BINARY(8)) + 0x7C +
CAST(FK2 AS BINARY(8)) + 0x7C +
CAST(FK3 AS BINARY(8)) + 0x7C +
CAST(FK4 AS BINARY(8)) + 0x7C +
CAST(FK5 AS BINARY(8)) + 0x7C +
CAST(FK6 AS BINARY(8)) + 0x7C +
CAST(FK7 AS BINARY(8)) + 0x7C +
CAST(FK8 AS BINARY(8)) + 0x7C +
CAST(FK9 AS BINARY(8)) + 0x7C +
CAST(FK10 AS BINARY(8)) + 0x7C +
CAST(FK11 AS BINARY(8)) + 0x7C +
CAST(FK12 AS BINARY(8)) + 0x7C +
CAST(FK13 AS BINARY(8)) + 0x7C +
CAST(FK14 AS BINARY(8)) + 0x7C +
CAST(FK15 AS BINARY(8)) + 0x7C +
CAST(FK16 AS BINARY(8)) + 0x7C +
CAST(FK17 AS BINARY(8)) + 0x7C +
CAST(FK18 AS BINARY(8)) + 0x7C +
CAST(FK19 AS BINARY(8)) + 0x7C +
CAST(FK20 AS BINARY(8))
)
)
FROM dbo.HASH_SMALL
OPTION (MAXDOP 1);
END;
';
DECLARE CreateProcsCurs CURSOR READ_ONLY FORWARD_ONLY LOCAL FAST_FORWARD
FOR SELECT [ProcName], [CodeToExec]
FROM @TestProcsToCreate;
OPEN [CreateProcsCurs];
FETCH NEXT
FROM [CreateProcsCurs]
INTO @ProcName, @CodeToExec;
WHILE (@@FETCH_STATUS = 0)
BEGIN
-- First: create VARBINARY version
SET @ProcToCreate = REPLACE(REPLACE(@ProcTemplate,
N'{{ProcName}}',
@ProcName),
N'{{CodeToExec}}',
@CodeToExec);
EXEC (@ProcToCreate);
-- Second: create NVARCHAR version (optional: built-ins only)
IF (CHARINDEX(N'.', @CodeToExec) = 0)
BEGIN
SET @ProcToCreate = REPLACE(REPLACE(REPLACE(@ProcToCreate,
N'dbo.RUN_' + @ProcName,
N'dbo.RUN_' + @ProcName + N'_NVC'),
N'BINARY(8)',
N'NVARCHAR(15)'),
N'0x7C',
N'N''|''');
EXEC (@ProcToCreate);
END;
FETCH NEXT
FROM [CreateProcsCurs]
INTO @ProcName, @CodeToExec;
END;
CLOSE [CreateProcsCurs];
DEALLOCATE [CreateProcsCurs];
_
テスト1:衝突のチェック
_EXEC dbo.VERIFY_NO_COLLISIONS 1;
EXEC dbo.VERIFY_NO_COLLISIONS 2;
EXEC dbo.VERIFY_NO_COLLISIONS 3;
EXEC dbo.VERIFY_NO_COLLISIONS 4;
EXEC dbo.VERIFY_NO_COLLISIONS 5;
EXEC dbo.VERIFY_NO_COLLISIONS 6;
EXEC dbo.VERIFY_NO_COLLISIONS 7;
EXEC dbo.VERIFY_NO_COLLISIONS 8;
EXEC dbo.VERIFY_NO_COLLISIONS 9;
EXEC dbo.VERIFY_NO_COLLISIONS 10;
EXEC dbo.VERIFY_NO_COLLISIONS 11;
_
TEST 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つの問題はテストに組み込まれていませんが、理想的にはどのアプローチがを満たすかを判断するために調査する必要があります(= /// =)の要件。
その後削除されたコメントで、Paul Whiteは次のように述べています。
HASHBYTES
をCLRスカラー関数に置き換えることの1つの欠点-CLR関数はバッチモードを使用できないが、HASHBYTES
は使用できるようです。それはパフォーマンス面で重要かもしれません。
これは考慮すべきことであり、明らかにテストが必要です。 SQLCLRオプションが組み込みのHASHBYTES
よりもメリットがない場合は、関連するテーブルに既存のハッシュ(少なくとも最大のテーブル)をキャプチャする ソロモンの提案 に重みを追加します。 。
関数呼び出しで作成されたオブジェクトをプールしてキャッシュすることにより、おそらくパフォーマンスを向上させ、すべての.NETアプローチのスケーラビリティを向上させることができます。上記のPaul Whiteのコードの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は、静的/共有変数の使用を推奨しませんが、共有変数を読み取り専用としてマークすると、共有変数を使用できるようになります。もちろん、これはConcurrentDictionary
のようないくつかの変更可能な型の単一のインスタンスを割り当てることができるため、意味がありません。