时间戳字段上的 ORDER BY 使我的查询变慢。如何优化它?

Posted

技术标签:

【中文标题】时间戳字段上的 ORDER BY 使我的查询变慢。如何优化它?【英文标题】:ORDER BY on timestamp field makes my query slow. How to optimize it? 【发布时间】:2016-08-19 22:02:03 【问题描述】:

我正在开发一个存储“类似 Facebook”图像的 mysql 数据库:每个用户都关注一组“艺术家”,这些艺术家上传图像。

我写了一个查询,对于给定的用户,返回他关注的所有艺术家的图像,这些图像是 30 天之后的,按日期排序:

SELECT img.id, img.url, a.name

FROM (SELECT artist FROM user_follow_artist WHERE user = <USER_ID>) AS f -- need to work only on the followed artists
JOIN artistimage AS img ON img.artist = f.artist -- join on the table that contains the images (8.000.000 rows!)
JOIN artist AS a ON a.id = img.artist -- join on artist table to add details on image's owner

-- following: where + orderby that slow down everything
WHERE img.uploadDate > NOW() - INTERVAL 30 DAY 
ORDER BY img.uploadDate DESC 
LIMIT 50

查询平均耗时 30 秒,用户关注艺术家越多,耗时越多。 以下是一些细节:

正如所写,artistimage 表有 800 万行大 如果我删除 ORDER BY 子句,时间会下降到 2-3 秒 img.uploadDate 是一个 TIMESTAMP,它是 INDEXED 引擎是 InnoDB

我希望索引 uploadDate 可以提高速度,但没有任何改变。我该如何解决这个问题?

编辑:表结构

artist
------------------
id (integer, primary)
name (string)

user_follow_artist
------------------
user (integer, foreign key on user.id, indexed)
artist (integer, foreign key on artist.id, indexed)

artistimage
------------------
id (integer, primary)
artist (integer, foreign key on artist.id, indexed)
url (string)
uploadDate (timestamp, indexed)

解释:

id      select_type     table               type        possible_keys       key         key_len     ref             rows    Extra
1       PRIMARY         <derived2>          ALL         NULL                NULL        NULL        NULL            327     Using temporary; Using filesort
1       PRIMARY         img                 ref         artist,uploadDate   artist      9           img.artist      36      Using where
1       PRIMARY         user                eq_ref      PRIMARY             PRIMARY     8           db.img.artist   1    
2       DERIVED         user_follow_artist  ref         PRIMARY,user        user        8                           327     Using index

如果我删除 ORDER BY,EXPLAIN 是相同的,但第一行没有 Using temporary; Using filesort

【问题讨论】:

你应该避免使用子查询,这里根本不需要它。并添加explain select .. 输出 准确了解这些表的索引方式以及它们之间是否存在任何 FK 关系也很有用。我不认为子查询是这里的问题,因为它选择相同的数据而不管顺序和限制。解释带有或不带有 order by 子句的计划肯定会有所帮助。 在使用它连接的表中索引艺术家字段可能会有所帮助。 我已经用表格结构更新了这个问题。解释即将到来...... 也添加了解释 【参考方案1】:

我认为您的“限制 50”让您感到困惑。它会为您提供没有条件的前 50 行,这些行总是会快速返回。 当您添加 where 和 order by 子句时,它将要求 MySQL 获取所有行,然后排序和过滤以获取结果。

我认为如果您取出 WHERE、ORDER LIMIT 50,您会发现查询需要很长时间才能获取所有行。

要加快速度,您可以尝试重新编写查询。 您可以为您的“(SELECT artist FROM user_follow_artist WHERE user = )”集制作一个临时表。

您可以像这样将 WHERE 子句移至 JOIN:

JOIN artistimage AS img ON img.artist = f.artist AND img.uploadDate > NOW() - INTERVAL 30 DAY

【讨论】:

我按照您的提示:移动了 WHERE 子句并创建了临时表。查询似乎快了一点,但仍然需要 15 秒......这是我能达到的最好的吗? 如果我需要速度并且有时间....我会考虑使用基于内存的 MySQL 实例。运行速度比在磁盘上快得多,但您需要保留数据库的磁盘副本。其他要做的事情是对 800 万行表进行分区...您需要决定使用哪些分区,并且可能需要重新编写查询以包含分区条件,以便查询跳转到正确的分区【参考方案2】:

首先,让我们简化查询,因为IN ( SELECT ... ) 没有很好地优化:

SELECT  img.id, img.url, a.name
    FROM  user_follow_artist AS f
    JOIN  artistimage AS img  ON img.artist = f.artist
    JOIN  artist AS a  ON a.id = img.artist
    WHERE  img.uploadDate > NOW() - INTERVAL 30 DAY
      AND  f.user = <user_id>
    ORDER BY  img.uploadDate DESC
    LIMIT  50

那么让我们得到最好的索引。但是,唉,您正在对一个表进行过滤,并对另一表进行排序和限制。所以,让我们添加一些可能有用的索引并希望最好:

如果优化器以WHERE f.user... 开头:

f: INDEX(user, artist)
img: INDEX(artist, uploadDate)

如果优化器以WHERE img.uploadDate... ORDER BY... 开头:

img: INDEX(uploadDate)
f: INDEX(artist, user)

请注意,其中大部分是“复合”索引,列的顺序很关键。

比起你提供的散文,我更喜欢看到SHOW CREATE TABLE

如果user_follow_artist 是一个多:多映射表,我强烈建议遵循here 中的提示。

【讨论】:

嗯...我简化了,然后我遵循WHERE img.uploadDate... ORDER BY... 的情况,所以我添加了缺失的索引,但没有任何变化。 “没有任何变化”——仍然很慢?请提供EXPLAIN SELECT ...SHOW CREATE TABLE【参考方案3】:

您不需要user_follow_artist 的子查询,该表应该按原样连接。尽管如此,问题是查询没有使用uploadDate 中的索引。试试这个:

SELECT
    img.id,
    img.url,
    a.name
FROM
    user_follow_artist AS f
    INNER JOIN
    (
        SELECT id, url, uploadDate
        FROM artistimage
        WHERE uploadDate > NOW() - INTERVAL 30 DAY
    ) img ON ( img.id = f.artist )
    INNER JOIN artist AS a ON ( a.id = img.id )
WHERE
    f.user = <USER_ID>
ORDER BY
    img.uploadDate DESC

如果子查询没有返回太多结果,这应该可以正常工作。

【讨论】:

以上是关于时间戳字段上的 ORDER BY 使我的查询变慢。如何优化它?的主要内容,如果未能解决你的问题,请参考以下文章

多对多字段上的 order_by 导致查询集中的重复条目

order by 使查询变慢

为啥 MySQL 查询在使用 LIMIT 和 Order BY 时会变慢?

mysql使用order by排序查询导致sql变慢

ORDER BY 时间戳,未来和过去之间为 NULL

order by 后有多个字段