乐趣区

关于mysql:求求你不要再用offset和limit了

优化慢 SQL 技巧

浏览大略 3 分钟

[toc]


前言

不再须要放心数据库性能优化的日子曾经一去不复返了。

随着时代的倒退,每个新企业家都心愿建设下一个 Facebook,并联合收集每个可能的数据点以提供更好的机器学习预测的心态,作为开发人员,咱们须要比以往更好地筹备咱们的 API,以提供牢靠,高效的端点,应该可能毫不费力地浏览大量数据。

如果您曾经进行了一段时间的后端或数据库体系结构,则可能曾经实现了分页查问,如下所示:

然而,如果你的确建设了这样的分页,很遗憾地说,这样做是错的。

你不以为然?没关系。Slack、Shopify 和 Mixmax 这些公司都在用咱们明天将要探讨的形式进行分页。

我想你很难找出一个不应用 OFFSET 和 LIMIT 进行数据库分页的人。对于简略的小型应用程序和数据量不是很大的场景,这种形式还是可能“应酬”的。

如果你想从头开始构建一个牢靠且高效的零碎,在一开始就要把它做好。

明天咱们将探讨曾经被宽泛应用的分页形式存在的问题,以及如何实现高性能分页。

1. OFFSET 和 LIMIT 有什么问题

正如咱们在上几段中简要探讨的那样,OFFSET 和 LIMIT 十分实用于数据使用量很少的我的项目。

然而,当数据库里的数据量超过服务器内存可能存储的能力,并且须要对所有数据进行分页,问题就会呈现。

为了实现分页,每次收到分页申请时,数据库都须要进行低效的全表扫描。

全表扫描

什么是全表扫描?全表扫描(也称为程序扫描)是在数据库中进行的扫描,其中程序读取表中的每一行,而后查看遇到的列是否符合条件。因为从磁盘进行大量的 I/O 读取(包含屡次搜查)以及低廉的磁盘到内存传输,这种类型的扫描被认为是最慢的。

这意味着,如果你有 1 亿个用户,OFFSET 是 5 千万,那么它须要获取所有这些记录 (包含那么多基本不须要的数据),将它们放入内存,而后获取 LIMIT 指定的 20 条后果。

也就是说,为了获取一页的数据:

10 万行中的第 5 万行到第 5 万零 20 行

须要先获取 5 万行。这么做是如许低效?

如果你不置信,能够看看这个例子:

https://www.db-fiddle.com/f/3…

在左侧面板中,您有一个根本架构,该架构将为咱们的测试插入 100.000 行,而在右侧,则是有问题的查问和咱们的解决方案。只需单击顶部的“运行”,而后比拟每个执行工夫。第一个查问:1 秒;(问题查问)至多须要 30 秒钟的工夫能力运行。

数据越多,状况就越糟。看看我对 10 万行数据进行的 PoC。

https://github.com/IvoPereira…

当初你应该晓得这背地都产生了什么:OFFSET 越高,查问工夫就越长。

2. 有什么解决方案

这是你应该应用的:

这是基于 游标的分页

你应该存储最初收到的主键(通常是一个 ID)和 LIMIT,而不是在本地存储以后的 OFFSET 和 LIMIT 并随每个申请传递它,因而查问最终可能与此相似。

为什么?因为通过显式传递最新的读取行,你能够依据无效的索引键通知数据库确切从哪里开始搜寻,而不用思考该范畴之外的任何行。

以上面的比拟为例:

针对咱们的优化版本:

接管到的记录完全相同,然而第一个查问破费了 12.80 秒,第二个查问破费了 0.01 秒。你能领会到差别吗?

要应用这种基于游标的分页,须要有一个惟一的序列字段 (或多个),比方惟一的整数 ID 或工夫戳,然而在某些特定状况下,这可能不合乎咱们的需要。

我的倡议是始终思考每种表体系结构的优缺点以及在每种表体系结构中须要执行哪种查问。

如果您须要在查问中解决大量相干数据,Rick James 的文章提供了更深刻的领导。
http://mysql.rjweb.org/doc.ph…

论断

这样做的次要要点是始终查看查问的性能(无论是 1k 行还是 1M 行)。可伸缩性至关重要,如果从一开始就正确施行,必定会防止未来呈现许多麻烦。

再优化

相似于查问 SELECT * FROM table_name WHERE id > 8000000 LIMIT 10;,这样的效率十分快, 因为主键上是有索引的, 然而这样有个毛病, 就是 ID 必须是间断的, 并且查问不能有 where 语句, 因为 where 语句会造成过滤数据.

SELECT * FROM table_name WHERE id > 8000000 LIMIT 10;

笼罩索引优化

mysql 的查问齐全命中索引的时候,称为笼罩索引, 是十分快的,因为查问只须要在索引上进行查找,之后能够间接返回,而不必再回数据表拿数据。因而咱们能够先查出索引的 ID,而后依据 Id 拿数据。

select * from (select id from table_name limit 1000000,100) a left join table_name b on a.id = b.id;

参考起源:https://hackernoon.com/please…

pub 哥 2020 年初常识清单:

退出移动版