使用ORDERBY时MySQL慢速JOIN查询

问题描述 投票:5回答:5

我有这个查询的问题:

SELECT a.*
FROM smartressort AS s
JOIN smartressort_to_ressort AS str
    ON s.id = str.smartressort_id
JOIN article_to_ressort AS atr
    ON str.ressort_id = atr.ressort_id
JOIN article AS a FORCE INDEX (source_created)
    ON atr.article_id = a.id    
WHERE
    s.id = 1
ORDER BY
    a.created_at DESC
LIMIT 25;

这个很慢,有时需要14秒。

EXPLAIN显示:

1   SIMPLE  s   const   PRIMARY PRIMARY 4   const   1   Using index; Using temporary; Using filesort
1   SIMPLE  str ref PRIMARY,ressort_id  PRIMARY 4   const   1   Using index
1   SIMPLE  atr ref PRIMARY,article_id  PRIMARY 4   com.nps.lvz-prod.str.ressort_id 1262    Using index
1   SIMPLE  a   ALL NULL    NULL    NULL    NULL    146677  Using where; Using join buffer (flat, BNL join)

所以最后一个“全部”类型真的很糟糕。但我已经试图强制使用索引,没有运气。

文章表如下所示:

CREATE TABLE `article` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`node_id` varchar(255) NOT NULL DEFAULT '',
`object_id` varchar(255) DEFAULT NULL,
`headline_1` varchar(255) NOT NULL DEFAULT '',
`created_at` datetime(3) NOT NULL,
`updated_at` datetime(3) NOT NULL,
`teaser_text` longtext NOT NULL,
`content_text` longtext NOT NULL,
PRIMARY KEY (`id`),
KEY `article_nodeid` (`node_id`),
KEY `article_objectid` (`object_id`),
KEY `source_created` (`created_at`)
) ENGINE=InnoDB AUTO_INCREMENT=161116 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;

当我删除FORCE INDEX时,Explain变得更好,但查询仍然很慢。

解释没有力量指数:

1   SIMPLE  s   const   PRIMARY PRIMARY 4   const   1   Using index; Using temporary; Using filesort
1   SIMPLE  str ref PRIMARY,ressort_id  PRIMARY 4   const   1   Using index
1   SIMPLE  atr ref PRIMARY,article_id  PRIMARY 4   com.nps.lvz-prod.str.ressort_id 1262    Using index
1   SIMPLE  a   eq_ref  PRIMARY PRIMARY 4   com.nps.lvz-prod.atr.article_id 1   

而对于另一个smartressort id(3),它看起来像这样:

1   SIMPLE  s   const   PRIMARY PRIMARY 4   const   1   Using index; Using temporary; Using filesort
1   SIMPLE  str ref PRIMARY,ressort_id  PRIMARY 4   const   13  Using index
1   SIMPLE  atr ref PRIMARY,article_id  PRIMARY 4   com.nps.lvz-prod.str.ressort_id 1262    Using index
1   SIMPLE  a   eq_ref  PRIMARY PRIMARY 4   com.nps.lvz-prod.atr.article_id 1   

这里我们有一个Smartressort的13个Ressorts。行:1x1x13x1262x1 = 16.406

1)如何更快地提出此请求?

2)source_created指数有什么问题?

mysql sql mariadb query-performance
5个回答
4
投票

您在查询中使用的SELECT *很难看,这通常可以成为索引杀手。它可以排除索引的使用,因为您定义的大多数索引都不会覆盖SELECT *所要求的每一列。这个答案的方法是索引查询中的所有其他表,这将激励MySQL只对article表进行一次扫描。

CREATE INDEX idx1 ON article_to_ressort (article_id, ressort_id);
CREATE INDEX idx2 ON smartressort_to_ressort (ressort_id, smartressort_id);

这两个指数应加快加盟进程。请注意,我没有为smartressort表定义索引,假设它的id列已经是主键。我可能会从article表开始编写您的查询,然后向外加入,但它应该不重要。

此外,强制索引主要是一个坏主意或不必要。优化器通常可以确定何时最好使用索引。


2
投票

SELECT many columns FROM tables ORDER BY something LIMIT few是一个臭名昭着的表演反模式;它必须检索并排序一堆乱七八糟的行和列,只是为了丢弃结果集中除了几行之外的所有行。

诀窍是找出结果集中需要的article.id值,然后只检索这些值。它被称为延迟连接。

这应该得到你那套id值。可能没有必要加入smartressort表,因为smartressort_to_ressort包含您需要的id值。

                 SELECT a.id
                   FROM article a
                   JOIN article_to_ressort atr ON a.id = atr.article_id
                   JOIN smartressort_to_ressort str ON atr.ressort_id = str.ressort_id
                  WHERE str.smartressort_id = 1
                  ORDER BY a.created_at DESC
                  LIMIT 25

然后,您可以将其用作子查询来获取所需的行。

SELECT a.*
  FROM article a
 WHERE a.id IN (
                 SELECT a.id
                   FROM article a
                   JOIN article_to_ressort atr ON a.id = atr.article_id
                   JOIN smartressort_to_ressort str ON atr.ressort_id = str.ressort_id
                  WHERE str.smartressort_id = 1
                  ORDER BY a.created_at DESC
                  LIMIT 25
               )
 ORDER BY a.created_at DESC

第二个ORDER BY确保文章中的行具有可预测的顺序。那么,您的索引优化工作只需要应用于子查询。


0
投票

除了@TimBiegelsen的好答案,我建议修改你的source_created索引:

...
KEY `source_created` (`id`, `created_at`)

获得的是MySQL可以使用它进行排序,并且不需要获取所有16406行。它可能会或可能没有帮助,但值得尝试(可能使用明确的声明来使用它)


0
投票

首先:您可以从查询中删除smartressort表,因为它不会向其添加任何内容。

以下是您重写的查询。我们想要智能ressort#1的所有ressorts,然后是所有这些ressorts的文章。其中我们展示了最新的25个。

SELECT *
FROM article
WHERE id IN
(
  SELECT article_id
  FROM article_to_ressort 
  WHERE ressort_id IN
  (
    SELECT ressort_id
    FROM smartressort_to_ressort
    WHERE smartressort_id = 1
  )
)
ORDER BY created_at DESC
LIMIT 25;

现在需要哪些索引来帮助DBMS?从内部表(smartressort_to_ressort)开始。我们使用给定的smartressort_id访问所有记录,我们想获得相关的ressort_id。因此索引应按此顺序包含这两列。同样适用于article_to_ressort及其ressort_idarticle_id。最后,我们希望通过找到的文章ID和created_at订购文章。

CREATE INDEX idx1 ON smartressort_to_ressort (smartressort_id, ressort_id);
CREATE INDEX idx2 ON article_to_ressort (ressort_id, article_id);
CREATE INDEX idx3 ON article (id, created_at);

无论如何,这些索引只是对DBMS的提议。它可能会决定反对他们。对于article表上的索引尤其如此。 DBMS期望为一个smartressort_id访问多少行,即IN子句中可能有多少行?如果DBMS认为这可能大约是所有文章ID的10%,那么它可能已经决定顺序读取表,而不是混淆它通过索引这么多行。


0
投票

所以对我来说解决方案是这样的:

SELECT a.*
FROM article as a  USE INDEX (source_created)
where a.id in (
             SELECT atr.article_id
               from smartressort_to_ressort str 
               JOIN article_to_ressort atr  ON atr.ressort_id = str.ressort_id
              WHERE str.smartressort_id = 1
) 
ORDER BY a.created_at DESC
LIMIT 25;

这只需要~35ms。说明看起来像这样:

1   PRIMARY a   index   NULL    source_created  7   NULL    1   
1   PRIMARY <subquery2> eq_ref  distinct_key    distinct_key    4   func    1
2   MATERIALIZED    str ref PRIMARY,ressort_id,idx1 PRIMARY 4   const   1   Using index
2   MATERIALIZED    atr ref PRIMARY,article_id,idx2 PRIMARY 4   com.nps.lvz-prod.str.ressort_id 1262    Using index

即便如此,这个查询Explain看起来对我来说更好,但我不知道为什么:

explain SELECT a.*, NOW()
FROM article as a  USE INDEX (source_created)
where a.id in (SELECT atr.article_id
    FROM smartressort AS s
    JOIN smartressort_to_ressort AS str
    ON s.id = str.smartressort_id
    JOIN article_to_ressort AS atr
    ON str.ressort_id = atr.ressort_id
    WHERE s.id = 1
) 
ORDER BY a.created_at DESC
LIMIT 25;

输出:

1   PRIMARY s   const   PRIMARY PRIMARY 4   const   1   Using index
1   PRIMARY a   index   NULL    source_created  7   NULL    25  
1   PRIMARY str ref PRIMARY,ressort_id,idx1 PRIMARY 4   const   1   Using index
1   PRIMARY atr eq_ref  PRIMARY,article_id,idx2 PRIMARY 8   com.nps.lvz-prod.str.ressort_id,com.nps.lvz-prod.a.id   1   Using index; FirstMatch(a)
© www.soinside.com 2019 - 2024. All rights reserved.