以下について理解したい。
5つのテーブルのグループを合計と順序で結合するという複雑なクエリがあるとします。
脇に置いておきますクエリ自体に対する最適化。インデックスなど.LIMIT
を使用すると、パフォーマンスが大幅に向上しますか?すべてのクエリ(および結果)を処理する必要があると思いますbefore LIMITが適用されるので、LIMITを使用して結果のサブセットを取得しますが、これにより大幅な改善が見られますか?
LIMIT
を利用してパフォーマンスを向上させたい場合は、
LIMIT
の前にJOIN
を使用これらの原則は、それらを調整することができれば、長い道のりを行くことができます。
私はこれらの概念を このYouTubeビデオを見る(フランスのアクセントを注意深く聞く) によって学びました
これらの概念を使用して、いくつかのテーブルから上位40件の記事を取得するという非常に難しいStackOverflowの質問に答えました: 2011年5月12日:結合テーブルから単一の行をフェッチする 。
その質問に対する私の回答(2011年5月16日) で、次のクエリを記述して完全にテストしました。
SELECT
AAA.author_id,
AAA.date_created,
IFNULL(BBB.title,'<NO_TITLE>') title,
IFNULL(CCC.filename,'<NO-IMAGE>') filename,
IFNULL(CCC.date_added,'<NO-IMAGE-DATE>') image_date
FROM
(
SELECT
AA.id,
AA.date_added,
BB.author_id,
BB.date_created
FROM
(
SELECT
A.id,IFNULL(MAX(B.date_added),'1900-01-01 00:00:00') date_added
FROM (SELECT id FROM articles ORDER BY date_created DESC LIMIT 40) A
LEFT JOIN article_images B ON A.id = B.article_id
GROUP BY A.id
) AA
INNER JOIN articles BB USING (id)
) AAA
LEFT JOIN article_contents BBB ON AAA.id=BBB.article_id
LEFT JOIN article_images CCC
ON (AAA.id=CCC.article_id AND AAA.date_added=CCC.date_added)
ORDER BY AAA.date_created DESC;
LIMIT
を含むクエリの行に注意してください
FROM (SELECT id FROM articles ORDER BY date_created DESC LIMIT 40) A
このサブクエリは3レベルの深さに埋め込まれています。これにより、LIMIT
を使用して最新の40件の記事を取得できました。その後、必要なJOINを実行しました。
LIMIT
からの結果セットのサイズのため、サブクエリ内でLIMIT
を実行することが必ずしも適切であるとは限りません。すべての「アヒルが並んでいる」(クエリについて4つの原則を念頭に置いている)場合は、驚くほど良い結果が得られます。LIMIT
を実行するときは、クエリをできるだけ単純にします。クエリが実行されると、最初に、いくつかの演算子で構成されるプランに変換されます。演算子には、ブロックと非ブロックの2つの基本タイプがあります。 Non-Blocking Operatorは、要求された各行の子から1つまたは複数の行を取得します。一方、ブロッキングオペレーターは、出力を生成する前に、すべての子の行セット全体を読み込んで処理する必要があります。
ソートは典型的なブロッキングオペレーターです。したがって、select by order byは制限からあまりメリットがありません。ただし、必要なメモリが少なく、limit句が指定されている場合はより高速なソートアルゴリズムを利用できるRDBMSがあります。この場合、現在の最初のn行を格納し、以前の行が発生したときにそれらをメモリから移動するだけで十分です。これにより、パフォーマンスが大幅に向上する可能性があります。ただし、MySQLにその機能があるかどうかは100%わかりません。
どちらの方法でも、最初の出力行を生成する前に、制限ソートでも入力行セット全体を処理する必要があります。このアルゴリズムを実装すると、ソートを高速化できますが、クエリの残りの部分が最も高価な部分である場合は、提供された制限のため、合計実行時間は大幅に改善されません。
私の場合、理由が(まだ)わからなくてもはいと言えます。
SELECT g0_.id AS id_0, COUNT(a1_.id_tarifs) AS sclr_1
FROM groupe_jardinerie g0_
INNER JOIN articles_tarifs a1_
ON (a1_.groupe_jardinerie_id = g0_.id)
WHERE g0_.centrale_id = 511
AND a1_.date_fin_tarif >= '2018-01-29 10:46:35'
GROUP BY g0_.id;
(result set)
8 rows in set (**18.14 sec**)
時間に注意してください:18秒。大きな制限付きの同じリクエスト:
SELECT g0_.id AS id_0, COUNT(a1_.id_tarifs) AS sclr_1
FROM groupe_jardinerie g0_
INNER JOIN articles_tarifs a1_
ON (a1_.groupe_jardinerie_id = g0_.id)
WHERE g0_.centrale_id = 511
AND a1_.date_fin_tarif >= '2018-01-29 10:46:35'
GROUP BY g0_.id
LIMIT 100000000000;
(exact same result set)
8 rows in set (**1.32 sec**)
10倍以上速く!!!
EXPLAINは両方のリクエストに対して同じ結果を返します。
+----+-------------+-------+------------+--------+---------------------------------------------------+---------+---------+------------------------------+--------+----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------------------------------------------+---------+---------+------------------------------+--------+----------+----------------------------------------------+
| 1 | SIMPLE | a1_ | NULL | ALL | IDX_438010BBC10784EF | NULL | NULL | NULL | 795135 | 33.33 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | g0_ | NULL | eq_ref | PRIMARY,IDX_9CA5CF6758A1D71F,IDX_9CA5CF67670C757F | PRIMARY | 4 | phs.a1_.groupe_jardinerie_id | 1 | 50.00 | Using where |
+----+-------------+-------+------------+--------+---------------------------------------------------+---------+---------+------------------------------+--------+----------+----------------------------------------------+
LIMITは結果セットを制限するためにのみ干渉する必要があります(つまり、LIMIT 4を実行すると、上記の結果セットの最初の4行のみが取得されます)。