SQL Server 2005 での CHECKSUM() の衝突
-
06-07-2019 - |
質問
5,651,744 行のテーブルがあり、主キーは 6 列 (int x 3、smallint、varchar(39)、varchar(2)) で構成されています。このテーブルと、この主キーを共有し、列が追加されているが 3,700 万行ある別のテーブルのパフォーマンスを向上させたいと考えています。
ハッシュ キーを作成するための列を追加することを想定して分析を行ったところ、18,733 件の衝突が見つかりました。
SELECT SUM(CT)
FROM (
SELECT HASH_KEY
,COUNT(*) AS CT
FROM (
SELECT CHECKSUM(DATA_DT_ID, BANK_NUM, COST_CTR_NUM,
GL_ACCT_NUM, ACCT_NUM, APPN_CD) AS HASH_KEY
FROM CUST_ACCT_PRFTBLT
) AS X
GROUP BY HASH_KEY
HAVING COUNT(*) > 1
) AS Y
SELECT COUNT(*)
FROM CUST_ACCT_PRFTBLT
それは約2倍悪いです BINARY_CHECKSUM()
私がカバーしている宛先スペースの相対的な量が少ないことを考えると、これは高すぎるように見えますか (0.33%)?また、衝突がこれほど多い場合、時折発生する衝突を処理するために通常の列で結合する必要があることを考慮すると、行あたり 4 バイトの追加コストを払って結合の最初にこの製造されたキーで結合することに利点はありますか?
解決
チェックサムを追加すると、そのレベルのコリジョンで何かが得られるかはわかりません。 1回の衝突でも、間違ったデータに参加する可能性があるため、多すぎます。正しいレコードへの参加を保証できない場合、パフォーマンスは向上しますが、データの整合性が損なわれるのは無意味です。これは財務データのように見えるので、クエリが悪い結果を返さないことを本当に確認した方が良いでしょう。衝突が発生した場合、実際には間違ったアカウントの借方記入または貸方記入を行う可能性があります。
このルートを選択した場合、Marcは可能な限り事前計算を行うべきです(数百万のレコードテーブル内のすべてのレコードに発生する計算を追加しても、私の経験ではパフォーマンスが向上しない可能性があります)。おそらく、事前に計算された列を実行できる場合(そしてそれを最新の状態に保つためにトリガーが必要になります)、衝突しないようにするために他の6列すべてに参加する必要はないかもしれません。その後、パフォーマンスが低下する可能性があります。できることは、理論をテストすることだけです。ただし、衝突がないことを確認してください。
代わりに、サロゲートキーを使用してから、6つの自然キーフィールドで一意のインデックスを使用することを検討しましたか?その後、サロゲートキーに参加することで、パフォーマンスがかなり向上する可能性があります。 1つの代理キーではなく、6つの列(1つはvarchar)で結合するのは効率的ではありません。データのサイズから、これは非実稼働システムよりもリファクタリングが難しいかもしれませんが、永続的なパフォーマンスの問題を永続的に修正するにはダウンタイムの価値があるかもしれません。変更がどれほど複雑で、すべてのspまたはクエリをより良い結合に変更することがどれほど難しいかは、あなただけが知ることができます。ただし、試してみるのは現実的かもしれません。
他のヒント
これまでに多くの人々が注目しているのは、 Microsoft自身の入場。意味のある衝突がかなり発生する MD5
よりもさらにひどいです。
ハッシュ列を取得する場合は、 の使用を検討してください。
。
SHA1
が指定されたHASHBYTES SHA1
は、 MD5
や CHECKSUM
よりも意味のある衝突がはるかに少ないです。したがって、行が一意であるかどうかを判断するために CHECKSUM
を使用することは避けてください。むしろ、2つの値の忠実性をすばやく確認するためのものです。したがって、重複する行がない場合(PKである必要はありません)、 HASHBYTES
の衝突率は0%である必要があります。
HASHBYTES
は8000バイトを超えるものは切り捨てますが、PKはそれよりはるかに少ない(すべて連結されている)ので、問題はないはずです。
チェックサムがデータの 0.33% まで下がっていれば、正常に動作していると言えるでしょう...特にこの列を他の (インデックス付き) 列と組み合わせて使用する場合はそうです。
もちろん、インデックスとして効果を発揮するには、データの挿入/更新時に非クラスター化インデックスを使用してこの値を計算して保存する必要があるでしょう。
もちろん、問題の列に対する通常のスパニング インデックスでも、同様かそれ以上の効果が得られる可能性があります。
クエリが選択的であり、ラインテーブルのクラスター化インデックスが狭いか存在しない場合、ラインテーブルのチェックサムの非クラスター化インデックスが優れたパフォーマンスを提供するはずです。
ヘッダーテーブルに存在する条件を適用した後、チェックサムを使用して非クラスター化インデックスでインデックスシークを実行します。それでも結合にFKを含める必要がありますが、非チェックサム結合基準は、インデックス検索後、ブックマーク後検索に適用されます。非常に効率的。
インデックスシーク用に最適化したい。チェックサムはすでに高度に選択的です。 FKを追加すると、インデックスサイズと対応するI / Oが増加し、ブックマークのルックアップを完全に回避するのに十分な他のフィールドが含まれない限り、役に立ちません。
非クラスター化インデックスにはクラスター化キーまたはヒープポインターが含まれるため、a)小さなクラスター化キー(たとえば、int ID列-4バイトポインター)またはb)クラスター化インデックスをまったく使用しない(8バイトポインター)。
クエリが選択的でない場合、または行テーブルのクラスター化インデックスが巨大な場合(テーブル全体から数列を引いたもの)、チェックサムが役立つかどうかわかりません(おそらく、インデックスナビゲーションの高速化?)。いずれにせよ、クラスター化インデックスまたはカバーリングインデックスにする必要があります。ヘッダーテーブルが最初にチェックサムでクラスター化されていない場合、多くの並べ替えが行われます。
ストレージとインデックス作成のコストに余裕がある場合は、いくつかのインデックス(ヘッダーと詳細)をカバーする方法があります。
PRIMARY KEY
がクラスター化されている場合、作成する各インデックスにはこの PRIMARY KEY
が含まれます。
ハッシュ値に参加するには、次の手順を使用します。
- インデックスキーのハッシュ値を特定します
- インデックスデータ内の
PRIMARY KEY
値を見つけます -
クラスター化インデックスシーク
を使用して、テーブル内のPRIMARY KEY
行を見つけます
- インデックスデータ内の
PRIMARY KEY
に参加するには、ステップ 3
のみを使用します。
SQL Server
は、これを考慮に入れるのに十分スマートであり、次のように参加する場合:
SELECT *
FROM main_table mt
JOIN CUST_ACCT_PRFTBLT cap
ON cap.HASH_KEY = mt.HASH_KEY
AND cap.DATA_DT_ID = mt.DATA_DT_ID
AND …
WHERE mt.some_col = @filter_value
、単に HASH_KEY
のインデックスを使用せず、代わりに、単一の Clustered Index Seek
と Filter
を使用して作成しますハッシュ値が一致することを確認してください(常に一致します)。
概要:プライマリキー
に参加するだけです。
セカンダリインデックスを使用する場合は、まず無駄な HASH_KEY
検索を行う必要があります。その後、 PRIMARY KEY
に参加する必要があります。