15

私は MySQL バージョン 5.0.95 で Drupal 6 を使用しており、最新の記事の日付に基づいてコンテンツを表示するクエリの 1 つが遅くなり、使用頻度が高いためにサイトのパフォーマンスが完全に低下するという行き詰まりに陥っています。問題のクエリは次のとおりです。

     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 (公開済み) の記事を取得します。しかし、一時テーブルの使用とファイルソートの使用は、私がそれについて閲覧したことから、クエリが失敗することを意味しているようです。

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

私の推測では、問題のほとんどは、記事をカテゴリにマップする term_node テーブルに起因するものであり、記事 X が 5 つのカテゴリ C1....C5 に関連付けられている場合、そのテーブルには 5 つのエントリがあることを意味する多対多の関係テーブルです。このテーブルは、すぐに使える drupal のものです。

重いDBコンテンツを扱うことは私にとって新しいことであり、同様のクエリのいくつかを経験しています( 日付デスクで注文する場合、「一時的なものを使用する」とクエリが 遅くなります.MySQLパフォーマンスの最適化:datetimeフィールドによる注文) datetime フィールドが ORDER BY 句で別のキー (nid) と共に使用され、FORCE INDEX を試みた content_type_article。

    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 クエリはあまり役に立たなかったようです

+----+-------------+-------+--------+--------------------------+-----------------+---------+----------------------+-------+---------------------------------+
| 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                     |
+----+-------------+-------+--------+--------------------------+-----------------+---------+----------------------+-------+---------------------------------+

フィールド n.nid、ca.nid、ma.field_article_date_format_value はすべて索引付けされています。Limit 0,11 で DB をクエリすると、ORDER BY 句を使用すると約 7 ~ 10 秒かかりますが、ORDER BY 句を使用しない場合、クエリはほとんど 1 秒かかりません。データベース エンジンは MyISAM です。これに関するヘルプは大歓迎です。

このクエリを通常のクエリのように(日付でソートしないクエリと同じ速度で)取得するのに役立つ回答はどれも素晴らしいでしょう。nidとを組み合わせてクエリで使用する複合クエリを作成しようとした私の試みはfield_article_date_format_value、原因を解決しませんでした。問題に関する追加情報と新しい提案を提供することにオープンです。

4

5 に答える 5

6

クエリと説明を見ると、結合によって定義されたセット全体を返し、ステータス = 1 を適用する必要があるため、where 句に n.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;
于 2012-12-17T04:03:05.657 に答える
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);

そして、クエリから取得したEXPLAINの抜粋を次に示します(サンプルテーブルを作成した後、InnoDBエンジンを使用して、各テーブルのカバーインデックスを含めます)。

_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の出力全体を以下に示します。

+----+-------------+-------+-------+---------------+----------------+---------+---------------------+------+--------------------------+
| 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)

FORCE INDEXヒントを省略する以外は、クエリに変更を加えませんでした。クエリで参照されている他の2つのテーブルで作成した他の2つの「カバーインデックス」は次のとおりです。

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

CREATE INDEX term_node_ix9
    ON term_node (nid,tid);

nid(がテーブルのクラスタリングキーであるnode場合、ノードテーブルのカバーインデックスは必要ない場合があることに注意してください。)


2)結合の代わりに相関サブクエリを使用しますか?

前のアイデアで何も改善されない場合は、別の方法として、元のクエリが最大11行を返すため、結合操作を回避するためにクエリを書き直して、代わりに相関サブクエリを使用することができます。以下のクエリのようなもの。

このクエリは、元のクエリとは大幅に異なることに注意してください。違いは、このクエリでは、context_type_articleテーブルの行が1回だけ返されることです。結合を使用したクエリを使用すると、そのテーブルの1つの行をnodeおよびterm_nodeテーブルの複数の行と照合できます。これにより、同じ行が複数回返されます。これは、望ましいまたは望ましくないものと見なされる場合があります。これは、カーディナリティ、および結果セットが仕様を満たしているかどうかによって異なります。

 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

(このタイプの「または関連するサブクエリ」を使用するクエリは、結合操作を行う同等のクエリよりもパフォーマンスが大幅に低下する場合があります。ただし、場合によっては、特に行数が非常に限られている場合、このようなクエリのパフォーマンスが実際に向上することがあります。戻ってきた。)

そのクエリのexplain出力は次のとおりです。

+----+--------------------+-------+-------+---------------+----------------+---------+---------------------+------+--------------------------+
| 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)

ここでも、各アクセスは'Using index'であることに注意してください。これは、基になるテーブルのデータページにアクセスするのではなく、インデックスページから直接クエリが実行されることを意味します。


テーブルの例

あなたの質問からの情報に基づいて、私が作成して入力したテーブルの例(およびインデックス)を次に示します。

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;
于 2012-12-18T17:51:04.660 に答える
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/

試してみるべきことがいくつかあります。それらが機能するかどうかは完全にはわかりません。実験するデータがなければ、それを知ることは困難です。

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

最新の 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;

それらは

于 2012-12-13T03:00:01.553 に答える
2

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

MySQLは指定された制限を見つけるとすぐに停止するため、ORDERBYのない制限はほとんどの場合高速です。ORDER BYを使用すると、最初にすべてのレコードを検索して並べ替え、次に指定された制限を取得する必要があります。

簡単に試すことができるのは、WHERE条件をJOIN句に移動することです。そのフィルターは、結合されるテーブルに固有です。これにより、MySQLが誤って最適化しないようになります。

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

より複雑なことは、term_nodeテーブルでSELECTを実行し、そのテーブルでJOINを実行することです。これは派生テーブルと呼ばれ、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には派生テーブルに関するいくつかの制限があるため、これは機能しない可能性があります。回避策はありますが。

于 2012-12-13T02:46:56.430 に答える
1

事前に並べ替えられたインデックスを利用して、可能であれば、並べ替え操作をまったく回避したいと考えています。

これが可能かどうかを調べるには、データが単一のテーブルに非正規化されていると想像し、WHERE 句に含める必要があるすべてのものが SINGLE VALUE で指定可能であることを確認してください。たとえば、列の 1 つで IN 句を使用する必要がある場合、並べ替えは避けられません。

サンプル データのスクリーンショットを次に示します。

非正規化され、tid、status DESC、date DESC でソートされたサンプル データ

したがって、データを非正規化した場合、単一の値を使用して tid と status をクエリし、日付の降順で並べ替えることができます。その場合、次のインデックスが完全に機能することを意味します。

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

これがあれば、クエリは上位 10 行のみにヒットし、並べ替える必要はありません。

では、非正規化せずに同じパフォーマンスを得るにはどうすればよいでしょうか...

STRAIGHT_JOIN句を使用して、MySQL がテーブルから選択する順序を強制できるはずだと思います。最後にソートしているテーブルから選択するようにしたいと考えています。

これを試して:

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 にノード テーブルから選択させ、次に term_node テーブルから選択させ、次に content_type_article テーブル (並べ替え対象の列を含むテーブル) から選択させることです。

この最後の結合は最も重要な結合であり、データを並べ替えなくても 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);

(カバリングインデックスの場合)

私は、MySQL オプティマイザについて、データを並べ替えることなく content_type_article に供給される複数の「nid」列の値を処理するのに十分賢いかどうかを知るのに十分な知識がないため、MIGHT と言います。

論理的には、迅速に機能する必要があります。たとえば、5 つの nid 値が最終的な content_type_article テーブルに入力される場合、それぞれの上位 10 をインデックスから直接取得し、結果をまとめてマージし、最終的な上位を選択できるはずです。 10、つまり、現在表示されている 19006 全体の代わりに、このテーブルから合計 50 行が読み取られたことを意味します。

それがどうなるか教えてください。

それがうまくいく場合は、他のテーブルのインデックスをカバーして最初の 2 つの結合を高速化することで、さらなる最適化が可能になります。

于 2012-12-22T19:34:13.357 に答える