时间戳字段上的 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 使我的查询变慢。如何优化它?的主要内容,如果未能解决你的问题,请参考以下文章