MySQL连接到另一个表时不使用索引


11

我有两个表,第一个表包含CMS中的所有文章/博客文章。其中一些文章也可能出现在杂志上,在这种情况下,它们与另一个包含杂志特定信息的表具有外键关系。

这是这两个表的创建表语法的简化版本,其中删除了一些不必要的行:

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总共包含约25万篇文章,并且我编写了一个简单的Python脚本,如果他们想在本地复制此问题,可使用该脚本用示例数据填充测试数据库。

如果我从这些表中选择一个,则MySQL毫无问题地选择适当的索引或快速检索文章。但是,在简单查询中将两个表结合在一起时,例如:

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无法选择适当的查询,性能骤降。这是相关的扩展说明(执行时间超过一秒钟):

+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
| 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                     |
+----+-------------+--------------+--------+-----------------------------------+---------+---------+----------------------------------------+-------+----------+---------------------------------+
  • 编辑九月30:我可以WHERE从此查询中删除该子句,但EXPLAIN仍然看起来相同,并且查询仍然很慢。

一种可能的解决方案是强制建立索引。运行带有相同查询的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”一栏只保留两个或三个值,你真的可以丢弃索引键base_article_is_publishedis_published)..在我看来这是一个布尔类型..
雷蒙德Nijland

编辑答案
雷蒙德·

Answers:


5

对此该怎么办,因为数据已经在正确的排序中,所以应该不需要“使用临时文件;使用文件排序”。

您需要了解MySQL为什么需要“使用临时;使用文件排序”来消除这种需要的技巧。

有关消除需求的说明,请参见第二个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

效果很好,我之前也需要使用国家/城市表,请参见此处的示例数据示例http://sqlfiddle.com/#!2/b34870/41

编辑后,如果base_article.is_published = 1总是返回1条记录(例如您的解释说明,INNER JOIN交付表可能会提供更好的性能,例如下面的答案中的查询),您可能还需要分析此答案

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


救生答案!我JOIN仅在使用,但MySQL没有选择索引。非常感谢Raymond
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年

第二种重构方法非常好用,查询时间在我的表中从8秒急剧减少到0.3秒...谢谢先生!
andreszs
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.