MySQLは別のテーブルに対して結合するときにインデックスを使用しません


11

2つのテーブルがあります。最初のテーブルには、CMS内のすべての記事/ブログ投稿が含まれています。これらの記事の一部は雑誌にも掲載される場合があります。その場合、それらは雑誌固有の情報を含む別のテーブルと外部キーの関係を持っています。

以下は、これらの2つのテーブルの作成テーブル構文の簡略化されたバージョンで、いくつかの重要でない行が削除されています。

CREATE TABLE `base_article` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `date_published` datetime DEFAULT NULL,
  `title` varchar(255) NOT NULL,
  `description` text,
  `content` longtext,
  `is_published` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `base_article_date_published` (`date_published`),
  KEY `base_article_is_published` (`is_published`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `mag_article` (
    `basearticle_ptr_id` int(11) NOT NULL,
    `issue_slug` varchar(8) DEFAULT NULL,
    `rubric` varchar(75) DEFAULT NULL,
    PRIMARY KEY (`basearticle_ptr_id`),
    KEY `mag_article_issue_slug` (`issue_slug`),
    CONSTRAINT `basearticle_ptr_id_refs_id` FOREIGN KEY (`basearticle_ptr_id`) REFERENCES `base_article` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CMSには合計約250,000の記事が含まれており、この問題をローカルで再現したい場合に、テストデータベースにサンプルデータを入力するために使用できる簡単なPythonスクリプトを作成しました。

これらのテーブルの1つから選択した場合、MySQLは適切なインデックスを選択したり、記事をすばやく取得したりしても問題ありません。ただし、2つのテーブルが次のような単純なクエリで結合されている場合:

SELECT * FROM `base_article` 
INNER JOIN `mag_article` ON (`mag_article`.`basearticle_ptr_id` = `base_article`.`id`)
WHERE is_published = 1
ORDER BY `base_article`.`date_published` DESC
LIMIT 30

MySQLは適切なクエリの選択に失敗し、パフォーマンスは急落します。拡張された関連する説明は次のとおりです(実行時間が1秒を超えています):

+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
| id | select_type |    table     |  type  |           possible_keys           |   key   | key_len |                  ref                   | rows  | filtered |              Extra              |
+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
|  1 | SIMPLE      | mag_article  | ALL    | PRIMARY                           | NULL    | NULL    | NULL                                   | 23830 | 100.00   | Using temporary; Using filesort |
|  1 | SIMPLE      | base_article | eq_ref | PRIMARY,base_article_is_published | PRIMARY | 4       | my_test.mag_article.basearticle_ptr_id |     1 | 100.00   | Using where                     |
+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
  • 9月30日の編集:WHEREこのクエリから句を削除できますが、EXPLAINそれでも外観は同じで、クエリはまだ低速です。

解決策の1つは、インデックスを強制することです。同じクエリをFORCE INDEX (base_articel_date_published)実行すると、約1.6ミリ秒で実行されるクエリが生成されます。

+----+-------------+--------------+--------+---------------+-----------------------------+---------+-------------------------+------+-----------+-------------+
| id | select_type |    table     |  type  | possible_keys |             key             | key_len |           ref           | rows | filtered  |    Extra    |
+----+-------------+--------------+--------+---------------+-----------------------------+---------+-------------------------+------+-----------+-------------+
|  1 | SIMPLE      | base_article | index  | NULL          | base_article_date_published |       9 | NULL                    |   30 | 833396.69 | Using where |
|  1 | SIMPLE      | mag_article  | eq_ref | PRIMARY       | PRIMARY                     |       4 | my_test.base_article.id |    1 | 100.00    |             |
+----+-------------+--------------+--------+---------------+-----------------------------+---------+-------------------------+------+-----------+-------------+

いくつかの理由で、回避できる場合は、このクエリにインデックスを強制する必要がないようにしたいと思います。特に、この基本的なクエリは、さまざまな方法(によるフィルタリングなどissue_slug)でフィルタリング/変更できます。その後base_article_date_published、使用するのに最適なインデックスではなくなります。

誰かがこのクエリのパフォーマンスを改善するための戦略を提案できますか?


列 "is_published"が2つまたは3つの値しか保持しない場合、そのインデックスKEY base_article_is_publishedis_published)を本当にドロップできます。私に見えるのはブール型です
。–

答えを編集しました
Raymond Nijland 2013年

回答:


5

データはすでに正しいソートになっているため、これについては、「一時的な使用;ファイルソートの使用」の必要性がなくなります。

MySQLが「一時的な使用; filesortの使用」を必要とし、その必要性を取り除く理由を知っておく必要があります。

必要性の除去に関する説明については、2番目のsqlfriddleを参照してください

SELECT
      *
    FROM base_article

    STRAIGHT_JOIN 
      mag_article
    ON
      (mag_article.basearticle_ptr_id = base_article.id)

    WHERE
      base_article.is_published = 1

    ORDER BY
      base_article.date_published DESC

http://sqlfiddle.com/#!2/302710/2を参照してください

これはかなり前に必要でしたが、Country / cityテーブルの場合はこれも必要です。サンプルデータを使用して、こちらのデモを参照してくださいhttp://sqlfiddle.com/#!2/b34870/41

編集後、base_article.is_published = 1が常に1つのレコードを返す場合、この説明を分析する必要があるかもしれません。

/programming/18738483/mysql-slow-query-using-filesort/18774937#18774937


命を救う答え!私JOINだけが使用していたが、MySQLはインデックスを取得していなかった。レイモンド、ありがとう
Maximus

4

クエリをリファクタリングする

SELECT * FROM
(SELECT * FROM base_article
WHERE is_published = 1
ORDER BY date_published LIMIT 30) A
INNER JOIN mag_article B
ON A.id = B.basearticle_ptr_id;

または

SELECT B.*,C.* FROM
(SELECT id FROM base_article
WHERE is_published = 1
ORDER BY date_published LIMIT 30) A
LEFT JOIN base_article ON A.id = B.id
LEFT JOIN mag_article C ON B.id = C.basearticle_ptr_id;

インデックスを変更する

ALTER TABLE base_article DROP INDEX base_article_is_published;
ALTER TABLE base_article ADD INDEX ispub_datepub_index (is_published,date_published);

試してみる !!!


リファクタリング:機能しません。これLIMIT 30はサブクエリにあるためです(これらの30行のすべてがmag_articlesテーブルにも含まれるわけではありません)。をLIMIT外部クエリに移動すると、パフォーマンスは元のクエリと同じになります。インデックスの変更:MySQLはそのインデックスも使用しません。WHERE元のクエリから句を削除しても違いはないようです。
Joshmaker 2013

2番目のリファクタリング方法は信じられないほどうまくいき、クエリ時間は私のテーブルで8秒から0.3秒に劇的に短縮されました...ありがとうございます!!
andreszs 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.