为啥 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 更改为 请注意,分页结果中经常使用limit/offset,并且持有lastId根本不可能,因为用户可以跳转到任何页面,而不是总是下一页。换句话说,偏移量通常需要根据页面和限制动态计算,而不是遵循连续的模式。 我在mysql.rjweb.org/doc.php/pagination中更详细地谈论“记住你离开的地方” 男人。你是一个活的救星。当我尝试您的答案时,我现在有 5 百万数据需要大约 90 分钟来处理所有带有偏移和限制的数据。该死的,它只需要 9 分钟来处理Thankyou man。谢谢!!【参考方案2】:

较高的偏移量会减慢查询速度是正常的,因为查询需要计算第一个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),并且会返回相同的结果,前提是没有丢失 ids(即间隙)。

【讨论】:

这是正确的。但是由于它受“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 页面大小?

如果数组大小发生变化以及定义的宏如何在此处计算偏移量,为啥 C 结构中的字符数组的偏移量会有所不同? [复制]

具有查询性能的 MySQL 限制语法

Postgres 不使用不同的查询计划来获得更高的偏移量

mysql 中的LIMIT用法

如何加快 LIMIT 子句中偏移量较大的 MySQL 查询?