2012-12-13 8 views
15

私は、MySQLバージョン5.0.95でDrupal 6を使用しています。最近の記事の日付に基づいてコンテンツを表示するクエリが遅くなり、使用頻度によってサイトのパフォーマンスが完全に失われます。ORDER BY句を使用するMySQLクエリを最適化できません

 SELECT n.nid, 
      n.title, 
      ma.field_article_date_format_value, 
      ma.field_article_summary_value 
     FROM node n 
INNER JOIN content_type_article ma ON n.nid=ma.nid 
INNER JOIN term_node tn   ON n.nid=tn.nid 
     WHERE tn.tid= 153 
     AND n.status=1 
    ORDER BY ma.field_article_date_format_value DESC 
     LIMIT 0, 11; 

は、クエリのEXPLAIN以下の結果を示しています:問題のクエリは以下の通りです。このクエリはまっすぐ前方に比較的単純なように見えたと

+----+-------------+-------+--------+--------------------------+---------+---------+----------------------+-------+---------------------------------+ 
| id | select_type | table | type | possible_keys   | key  | key_len | ref     | rows | Extra       | 
+----+-------------+-------+--------+--------------------------+---------+---------+----------------------+-------+---------------------------------+ 
| 1 | SIMPLE  | tn | ref | PRIMARY,nid    | PRIMARY | 4  | const    | 19006 | Using temporary; Using filesort | 
| 1 | SIMPLE  | ma | ref | nid,ix_article_date  | nid  | 4  | drupal_mm_stg.tn.nid |  1 |         | 
| 1 | SIMPLE  | n  | eq_ref | PRIMARY,node_status_type | PRIMARY | 4  | drupal_mm_stg.ma.nid |  1 | Using where      | 
+----+-------------+-------+--------+--------------------------+---------+---------+----------------------+-------+---------------------------------+ 

およびカテゴリに属する​​記事を検索します(用語)153であり、ステータス1(発行済み)である。しかし、明らかにテンポラリテーブルを使用してfilesortを使用すると、クエリは私がそれについてブラウズしたことから失敗することになります。

ORDER BY句からfield_article_date_format_valueを削除すると、Using temporaryが解決されます。 filesortを使用するとクエリの実行時間が短縮されますが、これは必須であり、トレードオフすることはできませんが、残念なことにサイトのパフォーマンスは同じです。

私の勘違いは、記事をカテゴリにマップするterm_nodeテーブルから来ており、記事Xが5つのカテゴリC1 ... C5に関連付けられている場合は多対多の関係テーブルですそのテーブルでは、このテーブルはすぐに使えるdrupalからのものです。重いDBのコンテンツへの対処

は私に何か新しいものと類似したクエリの一部を通過( When ordering by date desc, "Using temporary" slows down queryMySQL performance optimization: order by datetime field)である私が一緒にORDER BY句でのdatetimeフィールドに使用されcontent_type_articleのための複合インデックスを作成しようとしましたそれに別のキー(nid)を入れてFORCE INDEXを試みました。

SELECT n.nid, n.title, 
      ma.field_article_date_format_value, 
      ma.field_article_summary_value 
     FROM node n 
INNER JOIN content_type_article ma FORCE INDEX (ix_article_date) ON n.nid=ma.nid 
INNER JOIN term_node tn ON n.nid=tn.nid 
    WHERE tn.tid= 153 
     AND n.status=1 
    ORDER BY ma.field_article_date_format_value DESC 
    LIMIT 0, 11; 

結果とクエリをEXPLAIN以下はフィールドn.nid

+----+-------------+-------+--------+--------------------------+-----------------+---------+----------------------+-------+---------------------------------+ 
| id | select_type | table | type | possible_keys   | key    | key_len | ref     | rows | Extra       | 
+----+-------------+-------+--------+--------------------------+-----------------+---------+----------------------+-------+---------------------------------+ 
| 1 | SIMPLE  | tn | ref | PRIMARY,nid    | PRIMARY   | 4  | const    | 18748 | Using temporary; Using filesort | 
| 1 | SIMPLE  | ma | ref | ix_article_date   | ix_article_date | 4  | drupal_mm_stg.tn.nid |  1 |         | 
| 1 | SIMPLE  | n  | eq_ref | PRIMARY,node_status_type | PRIMARY   | 4  | drupal_mm_stg.ma.nid |  1 | Using where      | 
+----+-------------+-------+--------+--------------------------+-----------------+---------+----------------------+-------+---------------------------------+ 

多くを助けていないようでした、ca.nid、ma.field_article_date_format_valueはすべてインデックス化されています。 Limit 0,11でDBをクエリすると、ORDER BY句で約7〜10秒かかりますが、クエリを実行するとわずかに時間がかかります。データベースエンジンはMyISAMです。これに関する助けがあれば大いに感謝します。

このクエリを通常のクエリ(クエリと同じ速度で、日付順に並べ替える)にするのに役立つ回答は素晴らしいでしょう。 nidfield_article_date_format_valueの組み合わせとして複合クエリを作成してクエリで使用しようとしたが、その原因には役立たなかった。私は問題と新しい提案に関する追加情報を提供することに門を開いています。

答えて

2

MySQLは、最初にノードから選択するように指定していても、最初にterm_nodeテーブルから選択するようにクエリを "最適化"しています。データが分からないので、どちらが最適な方法か分かりません。 term_nodeテーブルは、〜19,000レコードがそこから選択されているので、あなたのパフォーマンスの問題がある場所です。

ORDER BYが指定されていない制限は、指定された制限が見つかるとすぐにMySQLが停止するため、ほぼ常に高速です。 ORDER BYでは、最初にすべてのレコードを見つけてソートし、指定された制限を取得する必要があります。

試してみるのは、WHERE条件をJOIN句に移動することです.JOIN句は、その場所にする必要があります。そのフィルタは、結合されるテーブルに固有です。これはMySQLが間違って最適化しないようにします。

INNER JOIN term_node tn ON n.nid=tn.nid AND tn.tid=153 

さらに複雑なことは、term_nodeテーブルでSELECTを実行してその上でJOINすることです。これはDERIVED TABLEと呼ばれ、EXPLAINでそのように定義されています。あなたが多対多であると言って以来、私はDISTINCTパラメータを追加してレコードの数を減らしました。

SELECT ... 
FROM node n 
INNER JOIN content_type_article ma FORCE INDEX (ix_article_date) ON n.nid=ma.nid 
INNER JOIN (SELECT DISTINCT nid FROM term_node WHERE tid=153) tn ON n.nid=tn.nid 
WHERE n.status=1 
ORDER BY ma.field_article_date_format_value DESC 
LIMIT 0,11 

MySQL 5.0には派生テーブルにいくつかの制限があります。これは機能しない可能性があります。回避策がありますが。

+0

応答のおかげで、term_nodeはN:Nですが、特定の用語に対する結果のノードは私の場合では区別されます。以前は派生テーブルのアプローチを試しましたが、クエリの実行は従来の手段とほとんど同じでした。 – optimusprime619

4

Using temporary; Using filesortは、MySQLが一時的な結果テーブルを作成し、必要な結果を得るためにソートする必要があることを意味します。これは、最新の投稿を取得するために使用しているORDER BY ... DESC LIMIT 0,n構成の結果であることがよくあります。それ自体は失敗の兆候ではありません。これを見てください:http://www.mysqlperformanceblog.com/2009/03/05/what-does-using-filesort-mean-in-mysql/

ここでいくつか試してみましょう。私は彼らが働くことを完全には確信していません。あなたのデータを実験することなく知るのは難しいです。

content_type_article.field_article_date_format_valueにBTREEインデックスがありますか?もしそうなら、それは助けになるかもしれません。

最新の11件の記事を表示していますか?または、先週または今月に登場した最新の記事11件を表示できますか?もしそうなら、あなたのWHERE句にこの行を追加することができます。それは、記事のマッチングのために時間の始めまでずっと見ていなくても、日付であなたのものをフィルタリングします。これは、長い間確立されたDrupalサイトを持っている場合に特に役立ちます。

AND ma.field_article_date_format_value >= (CURRENT_TIME() - INTERVAL 1 MONTH) 

最初に、INNER JOIN操作の順序を反転してみます。次に、結合基準にtid = 153を組み込みます。これはソートする必要がある一時テーブルのサイズを減らすかもしれません。次のようにすべて一緒に私の提案は、次のとおりです。

SELECT n.nid, 
      n.title, 
      ma.field_article_date_format_value, 
      ma.field_article_summary_value 
     FROM node n 
INNER JOIN term_node tn   ON (n.nid=tn.nid AND tn.tid = 153) 
INNER JOIN content_type_article ma ON n.nid=ma.nid 
    WHERE n.status=1 
     AND ma.field_article_date_format_value >= (CURRENT_TIME() - INTERVAL 1 MONTH) 
    ORDER BY ma.field_article_date_format_value DESC 
    LIMIT 0, 11; 

ものは

+0

ご意見ありがとうございます。はいBTreeが設置されました。懸念事項は、一時テーブルの使用を無効にしてインデックス作成を使用して並べ替える方法です。ビジネスロジックでは、最新の記事を表示する必要はありません。週または月 – optimusprime619

6

あなたのクエリを見てみると説明されている、それは句は非常に検索を行っているところでn.status = 1を持つように思えますジョインで定義されたセット全体を戻してからstatus = 1を適用する必要があるため、非効率です。すぐにWHEREによってフィルタリングされたterm_nodeテーブルからジョインを開始してから、ジョインに直ちにステータス条件を追加してください。試してみて、それがどうなるか教えてください。

SELECT n.nid, n.title, 
      ma.field_article_date_format_value, 
      ma.field_article_summary_value 
     FROM term_node tn 
INNER JOIN node n ON n.nid=tn.nid AND n.status=1 
INNER JOIN content_type_article ma FORCE INDEX (ix_article_date) ON n.nid=ma.nid 
    WHERE tn.tid= 153 
    ORDER BY ma.field_article_date_format_value DESC 
    LIMIT 0, 11; 
4

1)カバーインデックス

私は、単純な答えは "カバーインデックス" かもしれないと思います。

特にcontent_type_articleテーブルにあります。 「カバリングインデックス」は、ORDER BYの式を先頭の列として持ち、クエリによって参照されているすべての列を含みます。ここでは(私のテストテーブルの上に)私が作成したインデックスです:

CREATE INDEX ct_article_ix9 
    ON content_type_article 
     (field_article_date_format_value, nid, field_article_summary_value); 

は、そしてここで、各テーブルの上に被覆指数を含む、InnoDBエンジンを使用して、私は例のテーブルを構築した後、私は(クエリから取得するEXPLAINの抜粋です):

_type table type key    ref   Extra      
------ ----- ----- -------------- ----------- ------------------------ 
SIMPLE ma index ct_article_ix9 NULL   Using index 
SIMPLE n ref node_ix9   ma.nid  Using where; Using index 
SIMPLE tn ref term_node_ix9 n.nid,const Using where; Using index 

注意計画に示さない'Using filesort'がないこと、及び計画は、基本的には、クエリが必要とするすべてのデータがインデックスページから取得されることを意味するクエリで参照される各テーブルの'Using index'示します基礎となる表のページを参照する必要はありません。 (あなたのテーブルには、私のテストテーブルよりも多くの行を持っていますが、あなたはこのようになります計画を説明し得ることができる場合、あなたはより良いパフォーマンスを得ることができます。)完全のために


、ここでは全体のEXPLAIN出力があります:

FORCE INDEXヒントを省略する以外
+----+-------------+-------+-------+---------------+----------------+---------+---------------------+------+--------------------------+ 
| id | select_type | table | type | possible_keys | key   | key_len | ref     | rows | Extra     | 
+----+-------------+-------+-------+---------------+----------------+---------+-------- ------------+------+--------------------------+ 
| 1 | SIMPLE  | ma | index | NULL   | ct_article_ix9 | 27  | NULL    | 1 | Using index    | 
| 1 | SIMPLE  | n  | ref | node_ix9  | node_ix9  | 10  | testps.ma.nid,const | 11 | Using where; Using index | 
| 1 | SIMPLE  | tn | ref | term_node_ix9 | term_node_ix9 | 10  | testps.n.nid,const | 11 | Using where; Using index | 
+----+-------------+-------+-------+---------------+----------------+---------+---------------------+------+--------------------------+ 
3 rows in set (0.00 sec) 

私は、あなたのクエリに変更を加えていません。

CREATE INDEX node_ix9 
    ON node (`nid`,`status`,`title`); 

CREATE INDEX term_node_ix9 
    ON term_node (nid,tid); 

nidnodeテーブルの上にクラスタ化キーであれば、あなたがカバーインデックスを必要としないことに注意してください:ここで私は、クエリで参照他の二つのテーブルの上に作成した「インデックスがカバーする」他の2つです


2)ジョインの代わりに相関サブクエリを使用しますか?

以前の考えでは改善されない場合は、元のクエリで最大11行が返されるため、クエリをリライトして結合操作を回避し、代わりに相関サブクエリ。以下のような質問があります。

このクエリは元のクエリとは大きく異なる点に注意してください。違いは、このクエリでは、context_type_articleテーブルの行が1回だけ返されることです。ジョインを使用した問合せでは、その表の行は、nodeおよびterm_node表の複数の行に一致する可能性があり、同じ行が2回以上戻されます。これは、望ましいか望ましくないものと見なすことができ、実際には基数に依存し、結果セットが仕様を満たすかどうかによって異なります。

SELECT (SELECT n2.nid 
      FROM node n2 
      WHERE n2.nid = ma.nid 
      AND n2.status = 1 
      LIMIT 1 
     ) AS `nid` 
     , (SELECT n3.title 
      FROM node n3 
      WHERE n3.nid = ma.nid 
      AND n3.status = 1 
      LIMIT 1 
     ) AS `title` 
     , ma.field_article_date_format_value 
     , ma.field_article_summary_value 
    FROM content_type_article ma 
    WHERE EXISTS 
     (SELECT 1 
      FROM node n1 
      WHERE n1.nid = ma.nid 
      AND n1.status = 1 
     )     
    AND EXISTS 
     (SELECT 1 
      FROM term_node tn 
      WHERE tn.nid = ma.nid 
      AND tn.tid = 153 
     ) 
    ORDER BY ma.field_article_date_format_value DESC 
    LIMIT 0,11 

(時々、「orrelatedサブクエリ」のこのタイプを使用してクエリは、結合操作を行い同等のクエリよりも大幅に悪化した性能を持つことができます。しかし、いくつかのケースでは、このようなクエリは、実際にパフォーマンスが向上することができ、特に、与えられました。再び、各アクセスは、クエリが直接インデックスページから成立していることを意味する、rathe 'Using index'ある

+----+--------------------+-------+-------+---------------+----------------+---------+---------------------+------+--------------------------+ 
| id | select_type  | table | type | possible_keys | key   | key_len | ref     | rows | Extra     | 
+----+--------------------+-------+-------+---------------+----------------+---------+---------------------+------+--------------------------+ 
| 1 | PRIMARY   | ma | index | NULL   | ct_article_ix9 | 27  | NULL    | 11 | Using where; Using index | 
| 5 | DEPENDENT SUBQUERY | tn | ref | term_node_ix9 | term_node_ix9 | 10  | testps.ma.nid,const | 13 | Using where; Using index | 
| 4 | DEPENDENT SUBQUERY | n1 | ref | node_ix9  | node_ix9  | 10  | testps.ma.nid,const | 12 | Using where; Using index | 
| 3 | DEPENDENT SUBQUERY | n3 | ref | node_ix9  | node_ix9  | 10  | testps.ma.nid,const | 12 | Using where; Using index | 
| 2 | DEPENDENT SUBQUERY | n2 | ref | node_ix9  | node_ix9  | 10  | testps.ma.nid,const | 12 | Using where; Using index | 
+----+--------------------+-------+-------+---------------+----------------+---------+---------------------+------+--------------------------+ 
5 rows in set (0.00 sec) 

注:返される行の非常に限られた数)

はここではそのクエリの出力を説明するのですrは基礎となる表のデータページを訪問する必要がありません。あなたは本当に避けたい

CREATE TABLE `node` (`id` INT PRIMARY KEY, `nid` INT, `title` VARCHAR(10),`status` INT); 
CREATE INDEX node_ix9 ON node (`nid`,`status`,`title`); 
INSERT INTO `node` VALUES (1,1,'foo',1),(2,2,'bar',0),(3,3,'fee',1),(4,4,'fi',0),(5,5,'fo',1),(6,6,'fum',0),(7,7,'derp',1); 
INSERT INTO `node` SELECT id+7,nid+7,title,`status` FROM node; 
INSERT INTO `node` SELECT id+14,nid+14,title,`status` FROM node; 
INSERT INTO `node` SELECT id+28,nid+28,title,`status` FROM node; 
INSERT INTO `node` SELECT id+56,nid+56,title,`status` FROM node; 

CREATE TABLE content_type_article (id INT PRIMARY KEY, nid INT, field_article_date_format_value DATETIME, field_article_summary_value VARCHAR(10)); 
CREATE INDEX ct_article_ix9 ON content_type_article (field_article_date_format_value, nid, field_article_summary_value); 
INSERT INTO content_type_article VALUES (1001,1,'2012-01-01','foo'),(1002,2,'2012-01-02','bar'),(1003,3,'2012-01-03','fee'),(1004,4,'2012-01-04','fi'),(1005,5,'2012-01-05','fo'),(1006,6,'2012-01-06','fum'),(1007,7,'2012-01-07','derp'); 
INSERT INTO content_type_article SELECT id+7,nid+7, DATE_ADD(field_article_date_format_value,INTERVAL 7 DAY),field_article_summary_value FROM content_type_article; 
INSERT INTO content_type_article SELECT id+14,nid+14, DATE_ADD(field_article_date_format_value,INTERVAL 14 DAY),field_article_summary_value FROM content_type_article; 
INSERT INTO content_type_article SELECT id+28,nid+28, DATE_ADD(field_article_date_format_value,INTERVAL 28 DAY),field_article_summary_value FROM content_type_article; 
INSERT INTO content_type_article SELECT id+56,nid+56, DATE_ADD(field_article_date_format_value,INTERVAL 56 DAY),field_article_summary_value FROM content_type_article; 

CREATE TABLE term_node (id INT, tid INT, nid INT); 
CREATE INDEX term_node_ix9 ON term_node (nid,tid); 
INSERT INTO term_node VALUES (2001,153,1),(2002,153,2),(2003,153,3),(2004,153,4),(2005,153,5),(2006,153,6),(2007,153,7); 
INSERT INTO term_node SELECT id+7, tid, nid+7 FROM term_node; 
INSERT INTO term_node SELECT id+14, tid, nid+14 FROM term_node; 
INSERT INTO term_node SELECT id+28, tid, nid+28 FROM term_node; 
INSERT INTO term_node SELECT id+56, tid, nid+56 FROM term_node; 
1


例テーブルここ

は、あなたの質問からの情報に基づいて、私が構築され、人口(インデックスと一緒に)たとえばテーブルですあらかじめソートされた索引を利用することで、ソート操作はまったく起こりません。

これが可能かどうかを調べるには、データが1つのテーブルに非正規化されていることを想像し、WHERE句に含める必要があるすべてが単一値で指定可能であることを確認します。例えばいずれかの列でIN句を使用する必要がある場合は、ソートは不可避です。

は、ここではいくつかのサンプルデータのスクリーンショットです:だから

Sample data denormalised and sorted by tid, status DESC, date DESC

、あなたは、あなたのデータはdenormalised持っていたあなたは、日付の降順でソート単一の値を使用して、TID、ステータスに照会し、できれば。それは、その場合の次のインデックスが完全に動作します意味します:

create index ix1 on denormalisedtable(tid, status, date desc); 

あなたがこれを持っていた場合、クエリは唯一トップ10行を打つだろうし、ソートする必要はありませんでしょう。

そう - どのようにあなたがdenormalisingせずに同じパフォーマンスを得るのですか...

私はあなたがMySQLがテーブルから選択する順序を強制的にSTRAIGHT_JOIN句を使用することができるはずだと思う - あなたはそれを取得したいですあなたが最後にソートしているテーブルから選択してください。

これを試してみてください:

SELECT n.nid, 
     n.title, 
     ma.field_article_date_format_value, 
     ma.field_article_summary_value 
FROM node n 
STRAIGHT_JOIN term_node tn   ON n.nid=tn.nid 
STRAIGHT_JOIN content_type_article ma ON n.nid=ma.nid 
WHERE tn.tid= 153 
    AND n.status=1 
ORDER BY ma.field_article_date_format_value DESC 
LIMIT 0, 11; 

アイデアは、MySQLがcontent_type_articleテーブルからFINALLY THENノードテーブルから選択してterm_nodeテーブルからとして取得することです(あなたがソートされている列を含む表) 。

この最後の結合は最も重要なもので、索引を使用してデータをソートする必要なしにLIMIT句を使用できるようにする必要があります。

この単一のインデックスは、トリックを行う可能性があります:

create index ix1 on content_type_article(nid, field_article_date_format_value desc); 

または

create index ix1 on content_type_article(nid, field_article_date_format_value desc, field_article_summary_value); 

(カバーインデックス用)

を私はについて十分に知らないので、私は、MIGHTを言いますMySQLのオプティマイザは、データを使用せずにcontent_type_articleに入力される複数の 'nid'列の値を処理するのに賢明かどうかを知ります。

論理的には、すばやく動作するはずです。 5つのnid値が最後のcontent_type_articleテーブルに入力された場合、インデックスから直接トップ10を取得し、結果をマージして最終的なトップ10を選ぶことができます。あなたが現在見ている完全な19006のテーブルinsted。

どうすればいいか教えてください。

これが機能する場合は、他のテーブルのカバリングインデックスを使用して最初の2つのジョインを高速化することで、さらに最適化が可能になります。