为啥 MYSQL 更高的 LIMIT 偏移量会减慢查询速度?
Posted
技术标签:
【中文标题】为啥 MYSQL 更高的 LIMIT 偏移量会减慢查询速度?【英文标题】:Why does MYSQL higher LIMIT offset slow the query down?为什么 MYSQL 更高的 LIMIT 偏移量会减慢查询速度? 【发布时间】:2011-05-27 18:04:47 【问题描述】:简而言之:一个包含超过 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”的以下链接。 How can I speed up a MySQL query with a large offset in the LIMIT clause?的可能重复 相关链接:We need tool support for keyset pagination。如果您想知道使用偏移量或键集分页时数据库内部发生了什么,请查看这些幻灯片。 【参考方案1】:我自己也有同样的问题。鉴于您想要收集大量此类数据而不是一组特定的 30 个数据,您可能会运行一个循环并将偏移量增加 30。
所以你可以做的是:
-
保留一组数据的最后一个 id(30)(例如 lastId = 530)
添加条件
WHERE id > lastId limit 0,30
所以你总是可以有一个零偏移。您会对性能提升感到惊讶。
【讨论】:
如果有差距,这是否有效?如果您没有单个唯一键(例如复合键)怎么办? 这可能并不明显,只有当您的结果集按该键按升序排序时才有效(对于降序,相同的想法有效,但将 > lastid 更改为较高的偏移量会减慢查询速度是正常的,因为查询需要计算第一个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 performance: late row lookups【讨论】:
MySQL 的“早期行查找”行为是为什么它说这么久的答案。通过您提供的技巧,只有匹配的 id(直接通过索引)被绑定,从而节省了太多记录的不需要的行查找。成功了,万岁! @harald:“不工作”到底是什么意思?这是纯粹的性能改进。如果ORDER BY
没有可用的索引或索引涵盖了您需要的所有字段,则不需要此解决方法。
@f055:答案是“加速”,而不是“即时”。你读过答案的第一句话吗?
是否可以为 InnoDB 运行类似的东西?
@Lanti:请将其作为一个单独的问题发布,不要忘记用postgresql
标记它。这是一个特定于 MySQL 的答案。【参考方案3】:
MySQL 不能直接转到第 10000 条记录(或您建议的第 80000 个字节),因为它不能假设它是这样打包/排序的(或者它具有 1 到 10000 的连续值)。尽管实际上可能是这样,但 MySQL 不能假设没有漏洞/间隙/删除的 id。
因此,正如 bobs 所指出的,MySQL 必须先获取 10000 行(或遍历 id
上索引的第 10000 个条目),然后才能找到要返回的 30。
编辑:为了说明我的观点
请注意,虽然
SELECT * FROM large ORDER BY id LIMIT 10000, 30
会慢(呃),
SELECT * FROM large WHERE id > 10000 ORDER BY id LIMIT 30
将是 fast(er),并且会返回相同的结果,前提是没有丢失 id
s(即间隙)。
【讨论】:
这是正确的。但是由于它受“id”的限制,为什么当该id在索引(主键)内时需要这么长时间?优化器应直接引用该索引,然后获取具有匹配 id 的行(来自该索引) 如果你在 id 上使用了 WHERE 子句,它可以直接到达那个标记。但是,如果你对它进行限制,按 id 排序,它只是一个相对于开头的计数器,所以它必须贯穿整个过程。 很好的文章eversql.com/… 为我工作@Riedsio 谢谢。【参考方案4】:我发现了一个有趣的示例来优化 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/
它也适用于字符串
【讨论】:
这仅适用于不删除数据的表 @miro 仅当您假设您的查询可以在随机页面上进行查找时才如此,我不相信这张海报是假设的。虽然对于大多数现实世界的情况我不喜欢这种方法,但只要您始终基于获得的最后一个 id,这将适用于差距。【参考方案5】:这两个查询的耗时部分是从表中检索行。从逻辑上讲,LIMIT 0, 30
版本只需要检索 30 行。在LIMIT 10000, 30
版本中,评估10000 行并返回30 行。可以对我的数据读取过程进行一些优化,但请考虑以下几点:
如果查询中有 WHERE 子句怎么办?引擎必须返回所有符合条件的行,然后对数据进行排序,最后得到 30 行。
还要考虑在 ORDER BY 序列中未处理行的情况。必须对所有符合条件的行进行排序以确定要返回的行。
【讨论】:
只是想知道为什么要花时间来获取这 10000 行。该字段上使用的索引(id,它是一个主键)应该使检索这些行的速度与查找记录号的 PK 索引一样快。 10000,这反过来应该是快速寻找文件到该偏移量乘以索引记录长度,(即,寻找 10000*8 = 字节号 80000 - 假设 8 是索引记录长度) @Rahman - 计数超过 10000 行的唯一方法是一一越过它们。这可能只涉及一个索引,但索引行仍然需要时间来逐步完成。 no MyISAM 或 InnoDB 结构可以正确(在所有情况下)“寻找”记录 10000。10000*8 建议假定 (1) MyISAM、(2) 固定长度记录和 (3 ) 永远不会从表中删除任何内容。无论如何,MyISAM 索引是 BTree,所以它不起作用。 正如这个答案所说,我相信,真正慢的部分是行查找,而不是遍历索引(当然也会加起来,但远不及磁盘上的行查找)。根据为此问题提供的解决方法查询,我相信如果您选择索引之外的列,则往往会发生行查找——即使它们不是 order by 或 where 子句的一部分。我还没有找到为什么这是必要的原因,但这似乎是一些解决方法有帮助的原因。 我认为延迟是由索引树中的条目计数引起的,而不是查找起始索引(针对该索引树优化了 SQL 索引树,它被指向靠近目标行,而不去通过特定的行)。下一部分,读取行数,在使用WHERE ID > x
时同样“慢”。但无论如何,后者在大多数现实世界的应用程序中都是无用的。【参考方案6】:
对于那些对比较和数字感兴趣的人:)
实验 1:数据集包含大约 1 亿行。每行包含几个 BIGINT、TINYINT 以及两个 TEXT 字段(故意)包含大约 1k 个字符。
蓝色 :=SELECT * FROM post ORDER BY id LIMIT offset, 5
Orange := @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。
绿色 := 之前的蓝色 红色 := 之前的橙色【讨论】:
你的id
是主键还是非主键字段?
@ospider primary imho以上是关于为啥 MYSQL 更高的 LIMIT 偏移量会减慢查询速度?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 16 位地址和 12 位偏移量会导致 4KB 页面大小?