无法优化使用ORDER BY子句的MySQL查询

opt*_*619 15 mysql indexing sql-order-by query-optimization

我正在使用Drupal 6与MySQL 5.0.95版本,并且陷入僵局,其中我的一个基于最近文章日期显示内容的查询速度变慢,并且由于使用频率导致网站性能完全丧失.有问题的查询如下:

     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;
Run Code Online (Sandbox Code Playgroud)

查询的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                     |
+----+-------------+-------+--------+--------------------------+---------+---------+----------------------+-------+---------------------------------+
Run Code Online (Sandbox Code Playgroud)

该查询似乎相对简单和直接,并检索属于类别(术语)153且状态为1(已发布)的文章.但显然使用临时表和使用filesort意味着查询必然会从我学到的浏览中失败.

从ORDER BY子句中删除field_article_date_format_value可以解决Using temporary; 使用filesort减少了查询执行时间,但是是必需的,无法进行折衷,遗憾的是,同样适用于站点性能.

我的预感是,大多数问题来自term_node表,它将文章映射到类别,并且是一个多对多的关系表,这意味着如果文章X与5个类别C1相关联.C5它将在该表中有5个条目,这张桌子来自开箱即用的drupal.

处理繁重的数据库内容对我来说是新的,并且经历了一些类似的查询( 当按日期desc排序时,"使用临时"减慢查询速度, MySQL性能优化:按日期时间字段排序)我试图创建一个复合索引content_type_article其datetime字段在ORDER BY子句中使用,并且其中包含另一个键(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;
Run Code Online (Sandbox Code Playgroud)

结果和以下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                     |
+----+-------------+-------+--------+--------------------------+-----------------+---------+----------------------+-------+---------------------------------+
Run Code Online (Sandbox Code Playgroud)

字段n.nid,ca.nid,ma.field_article_date_format_value都已编入索引.使用ORDER BY子句查询带有限制0,11的DB大约需要7-10秒,但如果没有它,查询几乎不需要一秒钟.数据库引擎是MyISAM.任何有关这方面的帮助将不胜感激.

任何可以帮助我获得此查询的答案都像普通的那样(与没有按日期排序的查询速度相同)会很棒.我尝试用创建复合查询作为组合nidfield_article_date_format_value和使用查询没有帮助的原因.我愿意提供有关问题和任何新建议的其他信息.

小智 6

看一下你的查询和解释,似乎在where子句中使用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;
Run Code Online (Sandbox Code Playgroud)


Bre*_*ley 2

MySQL 正在“优化”您的查询,以便它首先从 term_node 表中选择,即使您指定首先从节点中选择。由于不了解数据,我不确定哪种方法是最佳方法。term_node 表肯定是性能问题所在,因为从那里选择了大约 19,000 条记录。

没有 ORDER BY 的限制几乎总是更快,因为 MySQL 一旦找到指定的限制就会停止。使用 ORDER BY,它首先必须找到所有记录并对它们进行排序,然后获取指定的限制。

尝试的简单方法是将 WHERE 条件移至 JOIN 子句中,这是它应该在的位置。该过滤器特定于要连接的表。这将确保 MySQL 不会错误地优化它。

INNER JOIN term_node tn ON n.nid=tn.nid AND tn.tid=153
Run Code Online (Sandbox Code Playgroud)

更复杂的事情是在 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
Run Code Online (Sandbox Code Playgroud)

MySQL 5.0 对派生表有一些限制,因此这可能不起作用。尽管有解决方法。