5

うまくいっていることを願っています。

このデータベースについて少し助けが必要です:

ここに画像の説明を入力

これは、投票を格納するデータベースです。ユーザーは好きなオーディオ トラックを選び、投票します。彼らは「上」または「下」に投票できます。やさしい。しかし、計算統計に関しては、毛むくじゃらになります。

メタ

これは、最も一般的に使用される統計 (キャッシングのようなもの) を格納するキー値スタイルのテーブルです。

mysql> SELECT * FROM Meta;
+-------------+-------+
| Key         | Value |
+-------------+-------+
| TRACK_COUNT | 2620  |
| VOTE_COUNT  | 3821  |
| USER_COUNT  | 371   |
+-------------+-------+

投票

投票テーブルは、投票自体を保持します。ここで唯一興味深いフィールドはType, の値で、次のことを意味します。

  1. 0- アプリは投票を行い、ユーザーはUIを使用してトラックに投票しました
  2. 1- インポートされた投票 (外部サービスから)
  3. 2- 統合投票。実際にはインポートされた投票と同じですが、実際には、このユーザーは外部サービスを使用してこのトラックに既に投票されており、現在はアプリを使用して自分自身を繰り返していることを示しています。

追跡

トラックは、それ自体の合計統計を保持します。好き、嫌いの量、外部サービスからの好き ( LikesRP)、外部サービスからの嫌い ( DislikesRP)、好き嫌いの調整。

アプリ

アプリは次の投票を取得する必要があります。

  1. 過去 7 日間で最も投票数が多かった 5 つのトラック
  2. 過去 7 日間で最も反対票が多かった 5 つのトラック
  3. 過去 7 日間で最も高く評価された 5 つのトラック。その投票は外部サービスからインポートされました ( Vote.Type = 1)
  4. 過去 1 か月間で最も投票数が多かった 100 曲

上位 100 位のトラックを取得するには、次のクエリを使用します。

SELECT
    T.Hash,
    T.Title,
    T.Artist,
    COALESCE(X.VotesTotal, 0) + T.LikesAdjust as VotesAdjusted
FROM (
    SELECT
        V.TrackHash,
        SUM(V.Vote) AS VotesTotal
    FROM
        Vote V
    WHERE
        V.CreatedAt > NOW() - INTERVAL 1 MONTH AND V.Vote = 'up'
    GROUP BY
        V.TrackHash
    ORDER BY
        VotesTotal DESC
) X
RIGHT JOIN Track T
    ON T.Hash = X.TrackHash
ORDER BY
    VotesAdjusted DESC
LIMIT 0, 100;

このクエリは正常に機能しており、調整を受け入れています (クライアントはリスト内のトラック位置を調整したいと考えていました)。ほぼ同じクエリを使用して、賛成/反対の投票数が最も多い 5 つのトラックを取得します。タスク #3 のクエリは次のとおりです。

SELECT
    T.Hash,
    T.Title,
    T.Artist,
    COALESCE(X.VotesTotal, 1) as VotesTotal
FROM (
    SELECT
        V.TrackHash,
        SUM(V.Vote) AS VotesTotal
    FROM
        Vote V
    WHERE
        V.Type = '1' AND
        V.CreatedAt > NOW() - INTERVAL 1 WEEK AND
        V.Vote = 'up'
    GROUP BY
        V.TrackHash
    ORDER BY
        VotesTotal DESC
) X
RIGHT JOIN Track T
    ON T.Hash = X.TrackHash
ORDER BY
    VotesTotal DESC
LIMIT 0, 5;

問題は、最初のクエリの実行に約 2 秒かかり、投票数が 4,000 未満であることです。年末までに、この数字は約 200,000 票になり、おそらくこのデータベースは機能しなくなります。というわけで、このパズルを解く方法を考えています。

そして今、私はこれらの質問に行き着きました:

  1. データベースの設計を間違えたのでしょうか? つまり、もっと良くなるでしょうか?
  2. クエリを間違えましたか?
  3. 他に改善できることはありますか?

最初に行ったのはキャッシングです。しかし、OK、これで問題は劇的に解決します。しかし、私は SQL 関連のソリューションに興味があります (常に完璧に傾倒しています)。

2 番目に思いついたのは、これらの計算値をMetaテーブルに入れ、投票手順中に変更することでした。しかし、私はそれを試すだけでは時間がありません。ちなみに、それだけの価値はありますか?または、エンタープライズ クラスのアプリはこれらの問題をどのように解決しますか?

ありがとう。

編集

インデックスを含めるのを忘れたなんて信じられません。どうぞ:

mysql> SHOW INDEXES IN Vote;
+-------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name                | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Vote  |          0 | UNIQUE_UserId_TrackHash |            1 | UserId      | A         |         890 |     NULL | NULL   |      | BTREE      |         |
| Vote  |          0 | UNIQUE_UserId_TrackHash |            2 | TrackHash   | A         |        4450 |     NULL | NULL   |      | BTREE      |         |
| Vote  |          1 | INDEX_TrackHash         |            1 | TrackHash   | A         |        4450 |     NULL | NULL   |      | BTREE      |         |
| Vote  |          1 | INDEX_CreatedAt         |            1 | CreatedAt   | A         |        1483 |     NULL | NULL   |      | BTREE      |         |
| Vote  |          1 | UserId                  |            1 | UserId      | A         |        1483 |     NULL | NULL   |      | BTREE      |         |
+-------+------------+-------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

mysql> SHOW INDEXES IN Track;
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name       | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Track |          0 | PRIMARY        |            1 | Hash        | A         |        2678 |     NULL | NULL   |      | BTREE      |         |
| Track |          1 | INDEX_Likes    |            1 | Likes       | A         |          66 |     NULL | NULL   |      | BTREE      |         |
| Track |          1 | INDEX_Dislikes |            1 | Dislikes    | A         |          27 |     NULL | NULL   |      | BTREE      |         |
+-------+------------+----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
4

1 に答える 1

3

これは非常に主観的な質問です。これは、正確な要件と、ここでは誰もデータに対して実行できないパフォーマンス テストに大きく依存するためです。しかし、私はあなたの質問に答えて、あなたに役立ついくつかの一般的な解決策を追加することができます:


データベースの設計を間違えたのでしょうか? つまり、もっと良くなるでしょうか?

いいえ。これは OLTP の理想的な設計です。


クエリを間違えましたか?

いいえ (ただし、ORDER BYサブクエリ内は冗長です)。Voteクエリの主な列はこの部分にあるため、クエリのパフォーマンスはテーブルのインデックスに大きく依存します。

SELECT  V.TrackHash, SUM(V.Vote) AS VotesTotal
FROM    Vote V
WHERE   V.CreatedAt > NOW() - INTERVAL 1 MONTH AND V.Vote = 'up'
GROUP BY V.TrackHash

TrackHash私は 2つCreatedAtVoteインデックスを提案しますType。20 万行はそれほど多くのデータではないため、適切なインデックスがあれば、先月のデータをクエリするのにそれほど時間はかかりません。


他に改善できることはありますか?

これは非常にバランスの取れた行為であり、続行するための最良の方法に関する正確な要件によって異なります. 問題にアプローチできる主な方法は 3 つあります。

1.現在のアプローチ(毎回投票テーブルをクエリする)

前に述べたように、このアプローチはアプリケーションに対してスケーラブルであるべきだと思います。利点は、メンテナンスが不要で、アプリケーションに送信されるすべてのデータが最新で正確であることです。不利な点はパフォーマンスです。データの挿入 (インデックスの更新のため) とデータの選択に少し時間がかかる場合があります。これは私の好みのアプローチです。

2. OLAP アプローチ

これには、次のような要約テーブルを維持することが含まれます。

CREATE TABLE VoteArchive
(       TrackHash           CHAR(40) NOT NULL,
        CreatedDate         DATE NOT NULL,
        AppMadeUpVotes      INT NOT NULL,
        AppMadeDownVotes    INT NOT NULL,
        ImportedUpVotes     INT NOT NULL,
        ImportedDownVotes   INT NOT NULL,
        MergedUpVotes       INT NOT NULL,
        MergedDownVotes     INT NOT NULL,
    PRIMARY KEY (CreatedDate, TrackHash)
);

これは、単純なクエリを実行することで毎晩入力できます

INSERT VoteArchive
SELECT  TrackHash,
        DATE(CreatedAt),
        COUNT(CASE WHEN Vote = 'Up' AND Type = 0 THEN 1 END),
        COUNT(CASE WHEN Vote = 'Down' AND Type = 0 THEN 1 END),
        COUNT(CASE WHEN Vote = 'Up' AND Type = 1 THEN 1 END),
        COUNT(CASE WHEN Vote = 'Down' AND Type = 1 THEN 1 END),
        COUNT(CASE WHEN Vote = 'Up' AND Type = 2 THEN 1 END),
        COUNT(CASE WHEN Vote = 'Down' AND Type = 2 THEN 1 END)
FROM    Votes
WHERE   CreatedAt > DATE(CURRENT_TIMESTAMP)
GROUP BY TrackHash, DATE(CreatedAt);

その後、ライブ データの代わりにこのテーブルを使用できます。日付がクラスター化インデックスの一部であるという利点があるため、日付によって制限されるクエリは非常に高速です。これの欠点は、このテーブルにクエリを実行すると、最後に入力された時点までの正確な統計しか得られないことですが、クエリははるかに高速になります。クエリを維持することも追加の作業です。ただし、ライブデータをクエリできない場合は、これが 2 番目の選択肢になります。

3.投票中に統計を更新する

完全を期すためにこれを含めていますが、このメソッドを使用しないでください. アプリケーションレイヤーまたはトリガーを介してこれを実現できます。「本番」テーブルにクエリを実行することなく最新のデータをクエリできますが、エラーが発生する可能性があります。真に支持する人に出会ったことはありません。このアプローチ。投票ごとに、非常に高速な挿入クエリをより長いプロセスに変える必要がある挿入/更新ロジックを実行する必要があります。メンテナンスの方法によっては、可能性があります (同時実行の問題は非常に小さいですが)。

4. 上記の組み合わせ

投票テーブルと同じ形式の 2 つのテーブルと、解決策 2 で設定された 1 つのテーブルを常に持つことができます。1 つの投票テーブルは今日の投票を保存するためのもので、もう 1 つは過去の投票用のものであり、それでも要約テーブルを維持できます。次に、今日のデータをサマリー テーブルと組み合わせて、大量のデータをクエリしなくても最新の結果を取得します。繰り返しますが、これは追加のメンテナンスであり、問​​題が発生する可能性が高くなります。

于 2013-02-18T18:35:13.553 に答える