为什么MYSQL更高的LIMIT偏移量会减慢查询速度?


173

简而言之,场景:一个拥有超过1600万条记录[2GB大小]的表。使用ORDER BY * primary_key *时,SELECT的LIMIT偏移量越高,查询的速度就越慢

所以

SELECT * FROM large ORDER BY `id`  LIMIT 0, 30 

花费不到

SELECT * FROM large ORDER BY `id` LIMIT 10000, 30 

那只能订购30条记录,而且无论如何都一样。因此,这不是ORDER BY的开销。
现在,当获取最新的30行时,大约需要180秒。如何优化该简单查询?


注意:我是作者。在上述情况下,MySQL不引用索引(PRIMARY)。请参见以下用户“ Quassnoi”的链接进行解释。
拉曼(Rahman)2010年

Answers:


197

通常,较高的偏移量会使查询速度变慢,因为查询需要从第一个OFFSET + LIMIT记录开始计数(并且只记录LIMIT其中的一个)。该值越高,查询运行的时间越长。

查询不能直接进行,OFFSET因为,第一,记录的长度可能不同,第二,删除的记录可能会有间隙。它需要检查并计数其途中的每条记录。

假设idPRIMARY KEY一个的MyISAM表,你可以使用这一招加快步伐:

SELECT  t.*
FROM    (
        SELECT  id
        FROM    mytable
        ORDER BY
                id
        LIMIT 10000, 30
        ) q
JOIN    mytable t
ON      t.id = q.id

看到这篇文章:


7
MySQL的“早排查找”行为就是它这么长时间讨论的答案。通过您提供的技巧,仅绑定匹配的ID(直接由索引),从而节省了太多记录的不必要的行查找。大功告成,万岁!
拉曼(Rahman)2010年

4
@harald:“不工作”到底是什么意思?这是纯粹的性能改进。如果没有可用的索引,ORDER BY或者该索引涵盖了您需要的所有字段,则不需要此替代方法。
Quassnoi

6
@ f055:答案是“加速”,而不是“即时”。您是否已阅读答案的第一句话?
Quassnoi 2012年

3
是否可以为InnoDB运行类似的内容?
NeverEndingQueue

3
@Lanti:请将其作为一个单独的问题发布,不要忘了用标记它postgresql。这是特定于MySQL的答案。
Quassnoi

220

我本人也有同样的问题。鉴于您想要收集大量的数据而不是特定的30个数据,您可能会运行一个循环并将偏移量增加30。

因此,您可以做的是:

  1. 保留一组数据的最后一个id(30)(例如lastId = 530)
  2. 添加条件 WHERE id > lastId limit 0,30

因此,您始终可以拥有零偏移。性能提高将使您惊讶。


如果存在差距,这行得通吗?如果您没有单个唯一键(例如复合键)怎么办?
xaisoft

8
可能并非所有人都清楚,这仅在您的结果集按该键以升序排序时才起作用(对于降序,相同的想法起作用,但将> lastid更改为<lastid。)主键,或另一字段(或组字段。)
Eloff

那个男人干得好!一个非常简单的解决方案,解决了我的问题:-)
oodavid 2013年

30
请注意,在分页结果中经常使用限制/偏移量,而持有lastId根本不可能,因为用户可以跳至任何页面,而并不总是跳至下一页。换句话说,通常需要根据页数和限制来动态计算偏移量,而不是遵循连续模式。
汤姆(Tom)


17

MySQL无法直接进入第10000条记录(或建议的第80000个字节),因为它不能假定它是像这样打包/排序的(或具有1到10000的连续值)。尽管实际上可能是这样,但MySQL无法假定没有漏洞/缺口/已删除的ID。

因此,正如鲍勃所指出的那样,MySQL必须先获取10000行(或遍历上的索引的第10000个条目id),然后才能找到要返回的30行。

编辑:以说明我的观点

请注意,尽管

SELECT * FROM large ORDER BY id LIMIT 10000, 30 

会比较

SELECT * FROM large WHERE id >  10000 ORDER BY id LIMIT 30 

将会更快(并且,如果没有缺失的ids(即空位),将返回相同的结果。


2
这是对的。但是,由于它受“ id”限制,所以当该id在索引(主键)内时,为什么要花这么长时间?优化程序应直接引用该索引,然后获取具有匹配ID(来自该索引)的行
Rahman 2010年

1
如果您在id上使用了WHERE子句,则可能会直接指向该标记。但是,如果按id对其进行限制,则它只是相对于开始的相对位置,因此必须横穿整个路径。
Riedsio 2010年

非常好的文章eversql.com/...
Pažout

为我工作@Riedsio谢谢。
mahesh kajale,

8

我找到了一个有趣的示例,用于优化SELECT查询的ORDER BY ID LIMIT X,Y。我有3500万行,所以花了2分钟才能找到一系列行。

这是窍门:

select id, name, address, phone
FROM customers
WHERE id > 990
ORDER BY id LIMIT 1000;

只需将WHERE和最后一个ID放在一起即可提高性能。对我来说是2分钟到1秒:)

其他有趣的技巧在这里:http : //www.iheavy.com/2013/06/19/3-ways-to-optimize-for-paging-in-mysql/

它也适用于字符串


1
这仅适用于未删除任何数据的表
miro

1
@miro只有在您假设查询可以在随机页面上进行查找的前提下才是正确的,我不相信此海报是在假设的。尽管我在大多数现实情况下都不喜欢这种方法,但是只要您始终以最后获得的ID为基础,这种方法就可以解决问题。
Gremio '18年

5

这两个查询的耗时部分是从表中检索行。从逻辑上讲,在该LIMIT 0, 30版本中,仅需要检索30行。在该LIMIT 10000, 30版本中,将评估10000行,并返回30行。在数据读取过程中可以做一些优化,但是请考虑以下几点:

如果查询中有WHERE子句怎么办?引擎必须返回所有符合条件的行,然后对数据进行排序,最后获得30行。

还考虑在ORDER BY序列中未处理行的情况。必须对所有符合条件的行进行排序,以确定要返回的行。


1
只是想知道为什么要花费时间来获取这10000行。在该字段上使用的索引(id是主键)应该使检索这些行的速度与为记录号寻求该PK索引的速度一样快。10000,这反过来应该被认为是将文件查找到该偏移量乘以索引记录长度而又快的方法(即,寻找10000 * 8 =字节号80000-假定索引记录长度为8)
Rahman 2010年

@Rahman-计数超过10000行的唯一方法是一步一步地遍历它们。这可能只涉及索引,但是索引行仍然需要一些时间才能完成。有没有的MyISAM或InnoDB的结构,其能够正确地(在所有情况下)“查找”,记录10000。10000 * 8建议假定(1)的MyISAM,(2)的固定长度的记录,以及(3)从未从表中删除任何。无论如何,MyISAM索引是BTrees,所以它不起作用。
里克·詹姆斯

就像这个答案所说的,我相信,真正慢的部分是行查找,而不是遍历索引(当然,索​​引也会累加,但远不及磁盘上的行查找)。基于针对此问题提供的解决方法查询,我相信,如果您选择索引之外的列,则行查找很容易发生-即使它们不是order by或where子句的一部分。我还没有找到为什么这样做的必要原因,但这似乎就是某些变通办法有所帮助的原因。
Gremio '18年

1

对于那些对比较和数字感兴趣的人:)

实验1:数据集包含约1亿行。每行包含几个BIGINT,TINYINT以及两个TEXT字段(故意),这些字段包含大约1k个字符。

  • 蓝色:= SELECT * FROM post ORDER BY id LIMIT {offset}, 5
  • 橙色:= @Quassnoi的方法。 SELECT t.* FROM (SELECT id FROM post ORDER BY id LIMIT {offset}, 5) AS q JOIN post t ON t.id = q.id
  • 当然,第三种方法... WHERE id>xxx LIMIT 0,5不会出现在这里,因为它应该是恒定的时间。

实验2:类似的东西,只不过一行只有3个BIGINT。

  • 绿色:=之前的蓝色
  • 红色:=之前的橙色

在此处输入图片说明

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.