2011-10-11 11 views
3

私は数百万のURLに対処する必要があるアプリケーションを作成しています。また、URLで検索する必要があります。ユニークなインデックス/制約なしにMySQLの重複行を防止しますか?

私のテーブルには、現在次のようになります。

CREATE TABLE Pages (
    id bigint(20) unsigned NOT NULL, 
    url varchar(4096) COLLATE utf8_unicode_ci NOT NULL, 
    url_crc int(11) NOT NULL, 
    PRIMARY KEY (id), 
    KEY url_crc (url_crc) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 

Bツリー索引はがちURLの非常に非効率的になるので、このような構造の背後にある考え方は、URLのCRC32ハッシュでルックアップを行うことです(InnoDBはハッシュインデックスをサポートしていません)。 CRC32の重複した結果は、完全なURLとの比較によってフィルタリングされます。検索クエリの例は、次のようになります。

SELECT id 
FROM Pages 
WHERE url_crc = 2842100667 
    AND url = 'example.com/page.html'; 

重複したエントリが挿入されないようにするという問題があります。アプリケーションは、新しいエントリを挿入する前に既存のエントリをデータベースで常にチェックしますが、同じ新しいURLに対する複数のクエリが同時に行われ、CRC32とURLが重複して入力される可能性があります。

私は巨大なので、URLにユニークなインデックスを作成したくありません。また、すべてのインサートにテーブルをロックしておくと、同時のインサートのパフォーマンスが損なわれることになります。この問題を効率的に解決する方法はありますか?

編集:使用法についてもう少し詳しく説明すると、URLに応じてコンテンツを検索するためのリアルタイムテーブルです。 URLを参照することで、URLの内部IDを見つけて、そのIDを使用してページのコンテンツを見つけることができます。新しいURLは常にシステムに追加されています。私はそれらのURLがどれほど手に入るか分かりません。新しいURLが参照されると、同じURLを参照する同時リクエスト(おそらくは毎秒数百回)によって脅かされる可能性があります。そのため、新しいコンテンツを追加する際の競合状態が懸念されます。結果は直ちに必要で、遅れを読むことはできません(少し遅れても問題ありません)。

開始するには、新しいURLが1日に数千回しか追加されませんが、来年にはよりスケーラブルなソリューションに移行するまでに何度も処理する必要があります。

URLにユニークなインデックスを使用した場合の1つの問題は、URLの長さがユニークインデックスの最大長を超えることができることです。 CRC32トリックを落としても、重複したURLを防ぐという問題は解決しません。

+2

URLのハッシュコピー(sha1?)を格納し、そのフィールドのインデックスを作成するとどうなりますか? DBの適切なトリガーを使用して、挿入/更新時にハッシュを更新/移入すると、メンテナンスのオーバーヘッドはごくわずかです。 –

+0

CRC32はURLのハッシュです。これはSHA1よりはるかに小さいハッシュです(4バイト対20バイト)。私はアプリケーション側でそれを計算しています。 –

+1

真実ですが、32ビットのみでは、衝突の確率が大幅に高まり、したがって偽陽性の偽薬が大幅に増加します。 –

答えて

0

ユニークインデックス(url_crc、url)の作成を検討しましたか? 「巨大」かもしれませんが、CRC32を使用して衝突する回数が増えると、おそらくページ検索機能の実行に役立ち、重複したURLも防止されます。

もう一つ考慮すべきことは、重複が挿入され、スクリプトで夜間に(またはトラフィックが少なくても)削除されることです。

+0

残念ながら、ページを参照するすべてのコンテンツは、同じページIDを使用して一緒に表示され、「失われていない」必要があります。これらのページIDはシステム全体に伝播するため、将来変更すると複雑になります。ユニークインデックスにも長さ制限があります。 –

0

ページテーブルに加えて、同じ列(PagesInsertA、PagesInsertB、およびPagesInsertC)を持つ3つの追加のテーブルを作成します。 URLを挿入するときは、Pagesに対して既存のエントリを確認し、存在しない場合は、URLをPagesInsertAに挿入します。その小さなテーブルで一意のインデックスを使用するか、後で重複を削除するステップを含めることができます(後述)。ローテーション時間の終わり(おそらく1分、制約については以下の説明を参照)、新しいURLをPagesInsertBに挿入するように切り替えます。重複を削除する(一意のインデックスを使用していない場合)、PagesInsertCでエントリを重複するエントリを削除する(そのテーブルは最初は空になるが、2番目ではない)、エントリを追加するPagesInsertAからPagesへ、空のPagesInsertCから。

2番目の期間の最後に、新しいURLをPagesInsertCに挿入するように切り替えます。 PagesInsertBについて上で説明した手順を実行します(違いは、PagesInsertAにも見つかったエントリを削除し、最後には空のPageInsertAを削除することです)。新しいURLが挿入されたテーブルの回転を続けます(A - > B - > C - > A - > ...)。

新しい挿入表へのURL挿入の切り替えと、前の挿入表からのクリーンアップ行の挿入との間に遅れが生じるため、少なくとも3つの挿入表が必要です。この例ではスイッチ間の時間として1分を使用しましたが、PagesInsertAからPagesへの挿入とPagesInsertC(たとえば)の空白がPagesInsertBとPagesInsertCに新しいURLを挿入する前に切り替わるまで、その時間を小さくすることができます。

2

実際にベンチマークを行い、btreeが問題であることが判明しましたか?私は時期尚早の最適化を感じる。

第2に、すべての文字列の開始が同じであることを心配している場合は、逆引きされた最後の文字を最初にインデックスすることです。私はMySQLがそれをネイティブに行うことはできないと思いますが、データを格納する前にそのデータを逆にすることができます。 MySQLを使用しないでください。

関連する問題