3

私は現在、最新のスコアと評価をリストで追跡するサイトを運営しています。リストには頻繁に更新される何千ものエントリがあり、リストはこれらのスコアと評価の列で並べ替えることができます。

このデータを取得するための私の SQL は、現在 (おおまかに) 次のようになっています。

SELECT e.*, SUM(sa.amount) AS score, AVG(ra.rating) AS rating
FROM entries e 
LEFT JOIN score_adjustments sa ON sa.entry_id = e.id
    HAVING sa.created BETWEEN ... AND ... 
LEFT JOIN rating_adjustments ra ON ra.entry_id = e.id
    HAVING ra.rating > 0 
ORDER BY score 
LIMIT 0, 10

テーブルの場所 (簡略化):

entries:
    id: INT(11) PRIMARY
    ...other data...

score_adjustments:
    id: INT(11), PRIMARY
    entry_id: INT(11), INDEX, FOREIGN KEY (entries.id)
    created: DATETIME
    amount: INT(4)

rating_adjustments:
    id: INT(11), PRIMARY
    entry_id: INT(11), INDEX, FOREIGN KEY (entries.id)
    rating: DOUBLE

約 300,000件のscore_adjustmentsエントリがあり、1 日あたり約 5,000 件のペースで増加しています。そのrating_adjustments約1/4です。

さて、私は DBA の専門家ではありませんが、常に呼び出しSUM()を行うのは良いことではないと推測しています。特に、数十万のレコードが含まれている場合はそうです。AVG()sara

私はすでにクエリのキャッシュを行っていますが、クエリ自体は高速でありながら、可能な限り最新の状態にしたいと考えています。このような大量の結合/集計クエリを最適化するソリューションを誰かが共有できるかどうか疑問に思っていましたか? 必要に応じて構造変更を行います。

編集1

クエリに関する詳細情報を追加しました。

4

2 に答える 2

2

パフォーマンスが心配な場合は、スコアと評価の列を対応するテーブルに追加し、トリガーを使用して参照されたテーブルへの挿入または更新時にそれらを更新できます。これにより、更新されるたびに新しい結果がキャッシュされ、毎回再計算する必要がなくなり、結果を取得するために必要な結合の量が大幅に削減されます...推測するだけですが、ほとんどの場合、クエリの結果はおそらく更新されるよりもはるかに頻繁にフェッチされます。

このSQLフィドルhttp://sqlfiddle.com/#!2/b7101/1をチェックして、トリガーとその効果を作成する方法を確認してください。挿入時にトリガーを追加しただけです。更新トリガーを追加するのも簡単です。データの削除削除のトリガーも追加します。

日時フィールドを追加しませんでした。between ... and ...パラメーターが頻繁に変更される場合は、毎回手動で変更する必要がある場合があります。それ以外の場合は、between句をscore_updateトリガーに追加するだけです。

于 2012-10-07T13:50:35.883 に答える
2

あなたのデータはひどくクラスター化されています。

InnoDB は、「近い」PK が物理的に近接している行を格納します。子テーブルはサロゲート PK を使用するため、それらの行は実質的にランダムに格納されます。「マスター」テーブルの特定の行の計算を行うときが来ると、DBMS はあらゆる場所をジャンプして、子テーブルから関連する行を収集する必要があります。

代理キーの代わりに、次のように、親の PK をリーディング エッジにして、より「自然な」キーを使用してみてください。

score_adjustments:
    entry_id: INT(11), FOREIGN KEY (entries.id)
    created: DATETIME
    amount: INT(4)
    PRIMARY KEY (entry_id, created)

rating_adjustments:
    entry_id: INT(11), FOREIGN KEY (entries.id)
    rating_no: INT(11)
    rating: DOUBLE
    PRIMARY KEY (entry_id, rating_no)

注: これはcreated、 の解像度が十分であり、rating_noが ごとに複数の評価を許可するために追加されたことを前提としていentry_idます。これは単なる例です。必要に応じて PK を変更できます。

これにより、同じ行に属する行entry_idが物理的に近くに格納されるように「強制」されるため、SUM または AVG は、PK/クラスタリング キーの範囲スキャンだけで、I/O をほとんど使用せずに計算できます。

または (たとえば、クラスタリングをサポートしない MyISAM を使用している場合)、クエリをインデックスでカバーして、クエリ中に子テーブルがまったく触れられないようにします。


さらに、設計を非正規化し、現在の結果を親テーブルにキャッシュできます。

  • SUM(score_adjustments.amount) を物理フィールドとして保存し、行が から挿入、更新、または削除されるたびにトリガーを介して調整しますscore_adjustments
  • SUM(rating_adjustments.rating) を「S」として保存し、 COUNT(rating_adjustments.rating) を「C」として保存します。行が に追加されたら、rating_adjustmentsそれを S に追加し、C をインクリメントします。実行時に S/C を計算して平均を取得します。更新と削除を同様に処理します。
于 2012-10-07T14:42:44.740 に答える