クエリの実行に特に長い時間(15秒以上)を要するクエリがあり、データセットが大きくなるにつれ、時間の経過とともに悪化します。私は過去にこれを最適化し、インデックス、コードレベルの並べ替え、その他の最適化を追加しましたが、さらに改良する必要があります。
SELECT sounds.*, avg(ratings.rating) AS avg_rating, count(ratings.rating) AS votes FROM `sounds`
INNER JOIN ratings ON sounds.id = ratings.rateable_id
WHERE (ratings.rateable_type = 'Sound'
AND sounds.blacklisted = false
AND sounds.ready_for_deployment = true
AND sounds.deployed = true
AND sounds.type = "Sound"
AND sounds.created_at > "2011-03-26 21:25:49")
GROUP BY ratings.rateable_id
クエリの目的は、sound id
と最新のリリース済みサウンドの平均評価。約1500の音と200万の評価があります。
sounds
にはいくつかのインデックスがあります
mysql> show index from sounds;
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+————+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+————+
| sounds | 0 | PRIMARY | 1 | id | A | 1388 | NULL | NULL | | BTREE | |
| sounds | 1 | sounds_ready_for_deployment_and_deployed | 1 | deployed | A | 5 | NULL | NULL | YES | BTREE | |
| sounds | 1 | sounds_ready_for_deployment_and_deployed | 2 | ready_for_deployment | A | 12 | NULL | NULL | YES | BTREE | |
| sounds | 1 | sounds_name | 1 | name | A | 1388 | NULL | NULL | | BTREE | |
| sounds | 1 | sounds_description | 1 | description | A | 1388 | 128 | NULL | YES | BTREE | |
+--------+------------+------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+
いくつかのratings
mysql> show index from ratings;
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+————+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+————+
| ratings | 0 | PRIMARY | 1 | id | A | 2008251 | NULL | NULL | | BTREE | |
| ratings | 1 | index_ratings_on_rateable_id_and_rating | 1 | rateable_id | A | 18 | NULL | NULL | | BTREE | |
| ratings | 1 | index_ratings_on_rateable_id_and_rating | 2 | rating | A | 9297 | NULL | NULL | YES | BTREE | |
+---------+------------+-----------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
こちらがEXPLAIN
です
mysql> EXPLAIN SELECT sounds.*, avg(ratings.rating) AS avg_rating, count(ratings.rating) AS votes FROM sounds INNER JOIN ratings ON sounds.id = ratings.rateable_id WHERE (ratings.rateable_type = 'Sound' AND sounds.blacklisted = false AND sounds.ready_for_deployment = true AND sounds.deployed = true AND sounds.type = "Sound" AND sounds.created_at > "2011-03-26 21:25:49") GROUP BY ratings.rateable_id;
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+——————+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+——————+
| 1 | SIMPLE | ratings | index | index_ratings_on_rateable_id_and_rating | index_ratings_on_rateable_id_and_rating | 9 | NULL | 2008306 | Using where |
| 1 | SIMPLE | sounds | eq_ref | PRIMARY,sounds_ready_for_deployment_and_deployed | PRIMARY | 4 | redacted_production.ratings.rateable_id | 1 | Using where |
+----+-------------+---------+--------+--------------------------------------------------+-----------------------------------------+---------+-----------------------------------------+---------+-------------+
一度取得した結果をキャッシュするので、サイトのパフォーマンスはそれほど問題ではありませんが、この呼び出しに時間がかかるため、キャッシュウォーマーの実行に時間がかかり、問題になり始めています。これは、1つのクエリで処理する数が多くないようです...
これをより良いパフォーマンスにするには、他に何ができますか?
クエリ、テーブル、およびWHERE AND GROUP BY句を調べた後、次のことをお勧めします。
推奨事項#1)クエリをリファクタリングする
クエリを再構成して3つのことを行いました:
これが私の提案したクエリです:
SELECT
sounds.*,srkeys.avg_rating,srkeys.votes
FROM
(
SELECT AA.id,avg(BB.rating) AS avg_rating, count(BB.rating) AS votes
(
SELECT id FROM sounds
WHERE blacklisted = false
AND ready_for_deployment = true
AND deployed = true
AND type = "Sound"
AND created_at > '2011-03-26 21:25:49'
) AA INNER JOIN
(
SELECT AAA.ratings,AAA.rateable_id
FROM ratings AAA
WHERE rateable_type = 'Sound'
) BB
ON AA.id = BB.rateable_id
GROUP BY BB.rateable_id
) srkeys INNER JOIN sounds USING (id);
推奨事項#2)WHERE句に対応するインデックスでサウンドテーブルにインデックスを付けます。
このインデックスの列には、静的値が最初で移動ターゲットが最後のWHERE句のすべての列が含まれます
ALTER TABLE sounds ADD INDEX support_index
(blacklisted,ready_for_deployment,deployed,type,created_at);
私はあなたが喜んで驚かれることを心から信じています。試してみる !!!
アップデート2011-05-21 19:04
カーディナリティを見たところです。痛い!!! rateable_idのカーディナリティーは1。少年、私は愚かだと思います!!!
アップデート2011-05-21 19:20
インデックスを作成するだけで、物事を改善できるでしょう。
アップデート2011-05-21 22:56
これを実行してください:
EXPLAIN SELECT
sounds.*,srkeys.avg_rating,srkeys.votes
FROM
(
SELECT AA.id,avg(BB.rating) AS avg_rating, count(BB.rating) AS votes FROM
(
SELECT id FROM sounds
WHERE blacklisted = false
AND ready_for_deployment = true
AND deployed = true
AND type = "Sound"
AND created_at > '2011-03-26 21:25:49'
) AA INNER JOIN
(
SELECT AAA.ratings,AAA.rateable_id
FROM ratings AAA
WHERE rateable_type = 'Sound'
) BB
ON AA.id = BB.rateable_id
GROUP BY BB.rateable_id
) srkeys INNER JOIN sounds USING (id);
アップデート2011-05-21 23:34
もう一度リファクタリングしました。これを試してください:
EXPLAIN
SELECT AA.id,avg(BB.rating) AS avg_rating, count(BB.rating) AS votes FROM
(
SELECT id FROM sounds
WHERE blacklisted = false
AND ready_for_deployment = true
AND deployed = true
AND type = "Sound"
AND created_at > '2011-03-26 21:25:49'
) AA INNER JOIN
(
SELECT AAA.ratings,AAA.rateable_id
FROM ratings AAA
WHERE rateable_type = 'Sound'
) BB
ON AA.id = BB.rateable_id
GROUP BY BB.rateable_id
;
アップデート2011-05-21 23:55
もう一度リファクタリングしました。これを試してください(前回):
EXPLAIN
SELECT A.id,avg(B.rating) AS avg_rating, count(B.rating) AS votes FROM
(
SELECT BB.* FROM
(
SELECT id FROM sounds
WHERE blacklisted = false
AND ready_for_deployment = true
AND deployed = true
AND type = "Sound"
AND created_at > '2011-03-26 21:25:49'
) AA INNER JOIN sounds BB USING (id)
) A INNER JOIN
(
SELECT AAA.ratings,AAA.rateable_id
FROM ratings AAA
WHERE rateable_type = 'Sound'
) B
ON A.id = B.rateable_id
GROUP BY B.rateable_id;
更新2011-05-22 00:12
あきらめたくない!!!
EXPLAIN
SELECT A.*,avg(B.rating) AS avg_rating, count(B.rating) AS votes FROM
(
SELECT BB.* FROM
(
SELECT id FROM sounds
WHERE blacklisted = false
AND ready_for_deployment = true
AND deployed = true
AND type = "Sound"
AND created_at > '2011-03-26 21:25:49'
) AA INNER JOIN sounds BB USING (id)
) A,
(
SELECT AAA.ratings,AAA.rateable_id
FROM ratings AAA
WHERE rateable_type = 'Sound'
AND AAA.rateable_id = A.id
) B
GROUP BY B.rateable_id;
更新2011-05-22 07:51
EXPLAINで200万行の評価が戻ってくるのが気になりました。その後、それは私を襲った。レート表には、rateable_typeで始まる別のインデックスが必要になる場合があります。
ALTER TABLE ratings ADD INDEX
rateable_type_rateable_id_ndx (rateable_type,rateable_id);
このインデックスの目標は、評価を操作する一時テーブルを200万回未満に減らすことです。その一時テーブルを大幅に小さく(少なくとも半分)できれば、クエリに期待が高まり、私の作業も速くなります。
そのインデックスを作成した後、元の提案されたクエリを再試行してください。
SELECT
sounds.*,srkeys.avg_rating,srkeys.votes
FROM
(
SELECT AA.id,avg(BB.rating) AS avg_rating, count(BB.rating) AS votes
(
SELECT id FROM sounds
WHERE blacklisted = false
AND ready_for_deployment = true
AND deployed = true
AND type = "Sound"
AND created_at > '2011-03-26 21:25:49'
) AA INNER JOIN
(
SELECT AAA.ratings,AAA.rateable_id
FROM ratings AAA
WHERE rateable_type = 'Sound'
) BB
ON AA.id = BB.rateable_id
GROUP BY BB.rateable_id
) srkeys INNER JOIN sounds USING (id);
UPDATE 2011-05-22 18:39:ファイナルワード
私はストアドプロシージャのクエリをリファクタリングし、高速化に関する質問に答えるのに役立つインデックスを追加しました。私は6つの賛成票を獲得し、回答を受け入れて、200の賞金を獲得しました。
また、別のクエリをリファクタリングし(限界結果)、インデックスを追加しました(劇的な結果)。私は2つの賛成票を獲得し、回答を受け入れてもらいました。
さらに別のクエリチャレンジのインデックスを追加し、1回賛成されました
これらの(あなたを含む)のようなすべての質問に答えたいと思ったのは、リファクタリングクエリで見たYouTubeビデオから発想を得ています。
もう一度ありがとう、@ coneybeare !!!ポイントや賞賛だけではなく、可能な限りこの質問に答えたいと思いました。さて、ポイントが貯まった気がします!!!
EXPLAIN出力をありがとう。このステートメントからわかるように、時間がかかるのは評価テーブルの全テーブルスキャンが原因です。 200万行をフィルタリングするWHEREステートメントはありません。
Ratings.typeにインデックスを追加することもできますが、私の推測では、カーディナリティは非常に低くなり、ratings
のかなりの数の行をスキャンすることになります。
または、mysqlにサウンドインデックスを使用するように強制する index hints を使用することもできます。
更新しました:
私の場合、sounds.created
にインデックスを追加します。これは、行をフィルタリングする可能性が最も高く、MySQLクエリオプティマイザがサウンドテーブルインデックスを使用するように強制するためです。長く作成された時間フレームを使用するクエリに注意してください(1年、3か月、サウンドテーブルのサイズによって異なります)。
これが"on-the-fly"の使用可能なクエリである必要がある場合は、オプションが少し制限されます。
私はこの問題を分割して征服することを提案します。
--
-- Create an in-memory table
CREATE TEMPORARY TABLE rating_aggregates (
rateable_id INT,
avg_rating NUMERIC,
votes NUMERIC
);
--
-- For now, just aggregate.
INSERT INTO rating_aggregates
SELECT ratings.rateable_id,
avg(ratings.rating) AS avg_rating,
count(ratings.rating) AS votes FROM `sounds`
WHERE ratings.rateable_type = 'Sound'
GROUP BY ratings.rateable_id;
--
-- Now get your final product --
SELECT
sounds.*,
rating_aggregates.avg_rating,
rating_aggregates.votes AS votes,
rating_aggregates.rateable_id
FROM rating_aggregates
INNER JOIN sounds ON (sounds.id = rating_aggregates.rateable_id)
WHERE
ratings.rateable_type = 'Sound'
AND sounds.blacklisted = false
AND sounds.ready_for_deployment = true
AND sounds.deployed = true
AND sounds.type = "Sound"
AND sounds.created_at > "2011-03-26 21:25:49";
サブクエリではなくJOINを使用します。あなたのサブクエリの試みは役に立ちましたか?
SHOW CREATE TABLE音\ G
SHOW CREATE TABLEの評価\ G
多くの場合、単一列のインデックスではなく、「複合」インデックスがあると効果的です。おそらくINDEX(type、created_at)
JOINの両方のテーブルでフィルタリングしています。これはパフォーマンスの問題である可能性があります。
約1500の音と200万の評価があります。
ratings
にauto_increment IDを設定し、サマリーテーブルを作成し、AI IDを使用して「中断」した場所を追跡することをお勧めします。ただし、平均を要約表に保管しないでください。
avg(ratings.rating)AS avg_rating、
代わりに、SUM(ratings.rating)を保持します。平均の平均は、平均を計算するために数学的に正しくありません。 (合計の合計)/(カウントの合計)は正しいです。