一意のインデックスを追加できない場合に重複を回避するために可能な方法は何ですか


10

同時実行の問題で立ち往生しています。

ユーザーが2から3のトランザクションを送信して、DBで重複してはならないデータを永続化するという典型的な問題です。重複するレコードがある場合、エラーを返す必要があります。

この問題は、ハッシュを格納する列にインデックス(一意)を追加できる場合は簡単です。

しかし、この場合、巨大なテーブル(おそらく数百万のレコード)があり、テーブルを変更することはできません。

実際、重複してはならないデータのハッシュを格納する列がありますが、一意のインデックスは設定されていません。

フラッシュの直前にJavaコードが存在するかどうかを確認しようとしていますが、それでも重複が発生します。

これに対する私の可能な解決策は次のとおりです。

  • 挿入しようとしているハッシュがテーブルに既に存在するかどうかをチェックするトリガーを作成します。
  • このテーブルの一意のインデックスを格納する別のテーブルを作成し、メインテーブルに外部キーを追加します。
  • 胎児の位置に座り泣く

ハッシュの衝突またはチェックのバグのため、ハッシュのチェックが失敗していますか?
candied_orange 2017

4
質問は聞き取れませんでした。したがって、数百万のレコードがあるすべての巨大なテーブルに対して一度インデックスを作成するのではなく、追加する次の数百万のレコードごとに読み取り、既存の数百万のレコードをダブルで探すことを好みますか?または、いくつかの情報を複製し、結合を追加して確認しますか?
クリストフ

問題は、この変更を行うために、サービスを毎月2時間以上停止できないいくつかの要件を満たすために、サービスに多くのスペースと長いダウンタイムが必要であることを警告してきました。このテーブルでメンテナンスを実行するのが最善の方法であることはわかっていますが、現時点では実行できないため、回避策が必要です。
rafuru

4
わかりません-トリガーを追加したり、インデックスを「エミュレート」するために別のテーブルを追加したりすると、既存のテーブルにインデックスを追加するよりもダウンタイムが短くなります。
Doc Brown、

2
@rafuru:一意のインデックスを作成する必要があると誰が言ったのですか?標準の一意でないインデックスは、おそらく同じハッシュ値を持つすべての行をすばやく見つけるために必要なすべてのものになります。
ドクターブラウン

回答:


3

簡単に解決できる可能性のあるシナリオがいくつかありますが、そうではない厄介なシナリオがあります。

値を入力するユーザーの場合、INSERTが問題を検出する前に、しばらくしてから同じ値を入力します。これは、あるユーザーが値を送信し、しばらくしてから別のユーザーが同じ値を送信する場合に機能します。

ユーザーが重複のある値のリストを送信する場合-たとえば、{ABC、DEF、ABC}-コードの1回の呼び出しで、アプリケーションは重複を検出してフィルタリングでき、おそらくエラーがスローされます。挿入する前に、DBに一意の値が含まれていないことも確認する必要があります。

トリッキーなシナリオは、あるユーザーの書き込みが別のユーザーの書き込みと同時にDBMS内にあり、同じ値を書き込んでいる場合です。その後、あなたはそれらの間の競争条件を持っています。DBMSは(ほとんどの場合-どちらを使用しているかはわかりません)、プリエンプティブマルチタスクシステムであるため、実行中のどの時点でもタスクを一時停止できます。つまり、user1のタスクは既存の行がないことを確認でき、user2のタスクは既存の行がないことを確認でき、user1のタスクはその行を挿入でき、user2のタスクはその行を挿入できます。各時点で、タスクはそれぞれが正しいことを行って満足しています。ただし、全体的にエラーが発生します。

通常、DBMSは問題の値にロックをかけることでこれを処理します。この問題では、新しい行を作成しているため、ロックするものはまだありません。答えはレンジロックです。これにより、現在存在するかどうかに関係なく、値の範囲がロックされます。ロックされると、その範囲は、ロックが解除されるまで別のタスクからアクセスできません。範囲ロックを取得するには、SERIALIZABLEの分離レベルを指定する必要があります。タスクがチェックした後に別のタスクが続けてこっそりと移動する現象は、ファントムレコードと呼ばれます

アプリケーション全体で分離レベルをSerializableに設定すると、影響があります。スループット低下します。過去に十分に機能していた他の競合状態は、エラーを表示し始める可能性があります。重複を引き起こすコードを実行する接続に設定し、アプリケーションの残りの部分はそのままにしておくことをお勧めします。

コードベースの代替方法は、書き込み前ではなく書き込みにチェックすることです。INSERTを実行し、そのハッシュ値を持つ行の数をカウントします。重複がある場合は、アクションをロールバックします。これは、いくつかの不可解な結果をもたらす可能性があります。タスク1がタスク2を書き込み、次にタスク1が重複をチェックして見つけたとします。最初でもロールバックします。同様に、両方のタスクが重複と両方のロールバックを検出する場合があります。しかし、少なくとも、操作するメッセージ、再試行メカニズムがあり、新しい重複はありません。プログラムフローを制御するために例外を使用するのと同じように、ロールバックは眉をひそめています。注同様にその全てトランザクションの作業は、複製を引き起こす書き込みだけでなく、ロールバックされます。また、同時実行性を低下させる可能性のある明示的なトランザクションが必要になります。ハッシュにインデックスがない限り、重複チェックはひどく遅くなります。あなたがするなら、あなたはそれをユニークなものにすることもできます!

あなたがコメントしたように、本当の解決策はユニークなインデックスです。これはあなたのメンテナンスウィンドウに収まるはずです(もちろんあなたはあなたのシステムを最もよく知っていますが)。ハッシュが8バイトであるとしましょう。1億行の場合、約1GBです。経験上、妥当なビットのハードウェアがこれらの多くの行を1〜2分で処理することが示唆されています。重複したチェックと削除はこれに追加されますが、事前にスクリプト化することができます。ただし、これは余談です。


2

実際、重複してはならないデータのハッシュを格納する列がありますが、一意のインデックスが設定されていません。

ハッシュの衝突をチェックすることは良い最初のステップですが再起動した場合、同じプログラムが同じデータで同じハッシュを生成することを保証できないことに注意してください。多くの「高速」ハッシュ関数は、プログラムの開始時にシードされる組み込みのprngを使用します。このアプリケーションで行うように、ハッシュが常に同じである必要がある場合は、暗号化ハッシュを使用します。良いまたは安全な暗号化ハッシュは必要ないことに注意してください。

2番目のステップは、実際にデータの等価性をチェックすることです。これは、データのエントロピーを(通常)削減しているため、最良のハッシュ関数でも衝突が発生する場合があるためです。

そう:

ステップ1:暗号ハッシュで衝突が発生するかどうかを確認する

ステップ2:ハッシュが一致する場合、実際のデータが同じであることを確認する


これがどのように質問に答えるかはわかりません。しばらくの間、利用可能なハッシュ列が確定的ハッシュ関数で満たされていると仮定しましょう(そうでなければ、それを利用しようとしても意味がありません)。私の理解では、問題はデータベースのそのハッシュ列にインデックスがないため、回答の最初のステップ-衝突があるかどうかの確認-でも、テーブルの新しいレコードごとにフルテーブルスキャンが必要です数百万のレコード。これはおそらく非常に遅くなります。
ドクターブラウン

インデックスを作成せずにできることは最高です。ハッシュスキャンは、少なくとも1つの列のみをチェックする必要があることを意味します。これは、チェックする必要がある多くの列をチェックするよりもはるかに高速です。
Turksarama

インデックスを作成できない場合でも(おそらくこの場合は可能です)、「このテーブルの一意のインデックスを格納する別のテーブルを作成し、外部キーをメインテーブルに追加する」というOPの当初の提案は、もっと感覚。
Doc Brown、

確定的ハッシュと暗号化ハッシュは2つの直交する概念ですが、そうではありませんか?暗号化ハッシュは決定論的ではない可能性があり、逆の場合、決定論的ハッシュは暗号化の強さではない可能性があります。
ニュートピア

それらは同じものではありませんが、直交していません。暗号化ハッシュは決定論的ハッシュのサブセットですが、特に何らかの理由で元に戻せるようにしたくない場合を除いて、実際に非暗号化決定論的ハッシュを作成する人は誰もいません。
Turksarama 2018年

2

一意の主キーで新しいテーブルを作成する

クライアント側で、単純な再送信を検出できるように、各レコードのGUIDの生成を開始します。

新しいレコードを新しいテーブルに入れて、少なくとも新しいデータが入るようにします。

新しいテーブル「CheckedAgainstOldData」に列がある

現在の遅いハッシュチェックを実行するバックエンドタスクで、古いデータの重複を見つけ、それに応じてフラグを設定し、この時点で重複を拒否して、クライアントに通知を送信します。

一方、データを古いテーブルから新しいテーブルに移動し、ハッシュチェックで重複をチェックしてGUIDを生成する別のバックエンドタスクがあります。

このタスクを(必要な場合)数日間実行したままにして、ダウンタイムなしでデータを転送できます。

転送が完了したら、遅い "CheckedAgainstOldData"プロセスをオフに切り替えることができます。すべてのデータを単一のテーブルに転送します。

率直に言って、問題があなたが説明するほど深刻で、ソフトウェアが古い場合、何千もの重複が発生します。


1

「ユーザー」からのデータがキーボードに座っている誰かを意味し、2人のユーザーが同時に同じデータを入力することからだましが発生すると想定します。トリガーの開始時にランダムな遅延を引き起こす関数を追加してみてください。新しいレコードをテーブルに書き込むのにかかる時間の最小値と、ナノメートル程度の最大値を指定します。そうすれば、重複リクエストを受け取ったときに最初のリクエストが実行され、存在トリガーが正しい結果をキックバックするはずです。(明確化:各呼び出しには、ALOHAプロトコルと同じ原則に沿って、独自のランダム遅延時間が必要です

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.