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

So

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

花费远远少于

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

只订购了30张唱片,两者都一样。这不是ORDER BY的开销。 现在,当获取最近的30行时,大约需要180秒。如何优化这个简单的查询?


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

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

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


MySQL不能直接到第10000条记录(或者你建议的第80000个字节),因为它不能假设它是这样打包/排序的(或者它有从1到10000的连续值)。尽管实际上可能是这样,但MySQL不能假设没有漏洞/空白/删除的id。

因此,正如bobs所指出的,MySQL将不得不在找到要返回的30行之前获取10000行(或在id上遍历索引的10000个条目)。

编辑:为了说明我的观点

请注意,尽管

SELECT * FROM large ORDER BY id LIMIT 10000, 30 

会很慢,

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

将是fast(er),并且将返回相同的结果,前提是没有缺失的id(即间隙)。


较高的偏移量会降低查询的速度,这是正常的,因为查询需要计算第一个OFFSET + LIMIT记录(并且只取其中的LIMIT)。该值越高,查询运行的时间就越长。

查询不能直接转到OFFSET,因为首先,记录的长度可能不同,其次,删除的记录之间可能存在空白。它需要检查和计数途中的每一条记录。

假设id是MyISAM表的主键,或者是InnoDB表中唯一的非主键字段,你可以使用下面的技巧来加快速度:

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

请看这篇文章:

MySQL ORDER BY / LIMIT性能:晚行查找


我自己也有同样的问题。假设你想要收集大量的数据,而不是特定的30个数据集,你可能会运行一个循环,并将偏移量增加30。

所以你可以做的是:

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

所以你总是可以有一个零偏移量。您会对性能的提高感到惊讶。


我发现了一个有趣的例子来优化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:数据集包含大约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 t.id = q.id 当然,第三种方法是……WHERE id>xxx LIMIT 0,5没有出现在这里,因为它应该是常数时间。

实验2:类似的事情,除了一行只有3个bigint。

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