使用ORDERBY时的MySQL Slow JOIN查询

时间:2019-03-12 10:32:32

标签: mysql sql mariadb query-performance

我对此查询有疑问:

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)

所以最后一个“ all”类型确实很糟糕。 但是我已经尝试过强制使用索引,但是没有运气。

文章表如下:

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时,解释会变好,但查询仍然很慢。

解释无力指数:

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个度假村。 行:1x1x13x1262x1 = 16.406

1)我该怎么做才能更快地发出此请求?

2)source_created索引有什么问题?

3 个答案:

答案 0 :(得分: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表开始并向外联接来编写您的查询,但这并不重要。

此外,强制索引通常不是一个好主意,也没有必要。优化器通常可以找出何时最好使用索引。

答案 1 :(得分: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确保文章中的行按可预测的顺序排列。因此,您的索引优化工作仅需要应用于子查询。

答案 2 :(得分:0)

除了@TimBiegelsen的好答案之外,我建议您修改import demjson start_str = 'e.exports={claims:' end_str = 'lastUpdated' with open('c:\\temp\\claims.txt','r',encoding="utf8") as claims_file: dirty_claims = claims_file.read() start_str_idx = dirty_claims.find(start_str) end_str_idx = dirty_claims.rfind(end_str) print('{} {}'.format(start_str_idx,end_str_idx)) claims_str = dirty_claims[start_str_idx + len(start_str):end_str_idx-1] claims = demjson.decode(claims_str) for claim in claims: print(claim) 索引:

source_created

好处是MySQL可以使用它进行排序,而无需获取所有16406行。它可能有帮助,也可能没有帮助,但是值得尝试(也许需要显式声明才能使用它)