求求你不要再用offset和limit了

简介: 求求你不要再用offset和limit了

前言

不再需要担心数据库性能优化的日子已经一去不复返了。

随着时代的发展,每个新企业家都希望建立下一个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/3JSpBxVgcqL3W2AzfRNCyq/1?ref=hackernoon.com


在左侧面板中,您有一个基本架构,该架构将为我们的测试插入100.000行,而在右侧,则是有问题的查询和我们的解决方案。只需单击顶部的“运行”,然后比较每个执行时间。第一个查询:1秒;(问题查询)至少需要30秒钟的时间才能运行。


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


https://github.com/IvoPereira/Efficient-Pagination-SQL-PoC?ref=hackernoon.com


现在你应该知道这背后都发生了什么:OFFSET 越高,查询时间就越长。

2. 有什么解决方案

这是你应该使用的:

这是基于游标的分页  

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

为什么?因为通过显式传递最新的读取行,你可以根据有效的索引键告诉数据库确切从哪里开始搜索,而不必考虑该范围之外的任何行。

以下面的比较为例:

针对我们的优化版本:

接收到的记录完全相同,但是第一个查询花费了12.80秒,第二个查询花费了0.01秒。你能体会到差异吗?


要使用这种基于游标的分页,需要有一个惟一的序列字段 (或多个),比如惟一的整数 ID 或时间戳,但是在某些特定情况下,这可能不符合我们的需求。


我的建议是始终考虑每种表体系结构的优缺点以及在每种表体系结构中需要执行哪种查询。


如果您需要在查询中处理大量相关数据,Rick James 的文章提供了更深入的指导。

http://mysql.rjweb.org/doc.php/lists

结论

这样做的主要要点是始终检查查询的性能(无论是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-dont-use-offset-and-limit-for-your-pagination-8ux3u4y

pub哥 2020 年终知识清单:

目录
相关文章
|
5月前
|
安全 Serverless API
函数计算操作报错合集之进不去页面,报错乱码 ResourceThrottled, Reserve resource exceeded limit 一般是由于什么导致的
在使用函数计算服务(如阿里云函数计算)时,用户可能会遇到多种错误场景。以下是一些常见的操作报错及其可能的原因和解决方法,包括但不限于:1. 函数部署失败、2. 函数执行超时、3. 资源不足错误、4. 权限与访问错误、5. 依赖问题、6. 网络配置错误、7. 触发器配置错误、8. 日志与监控问题。
|
6月前
|
SQL Java
一直傻傻分不清 count(*) count(id) count(1) 这次终于整明白了
一直傻傻分不清 count(*) count(id) count(1) 这次终于整明白了
66 0
|
6月前
|
存储 算法 前端开发
【算法训练营】队列 合集(1) 933. 最近的请求次数 || LCR 041. 数据流中的移动平均值 || 1700. 无法吃午餐的学生数量
【算法训练营】队列 合集(1) 933. 最近的请求次数 || LCR 041. 数据流中的移动平均值 || 1700. 无法吃午餐的学生数量
61 0
|
SQL 存储 关系型数据库
mysql查询 limit 1000,10 和limit 10 速度一样快吗?如果我要分页,我该怎么办?
mysql查询 limit 1000,10 和limit 10 速度一样快吗?如果我要分页,我该怎么办?
164 0
|
SQL Oracle 关系型数据库
【SQL开发实战技巧】系列(十五):查找最值所在行数据信息及快速计算总和百之max/min() keep() over()、fisrt_value、last_value、ratio_to_report
本篇文章讲解的主要内容是:***计算部门中那个工资等级的员工最多、通过返回部门10最大工资所在行的员工名称小案例来讲解max/min() keep() over()、通过查询工资最高的人小案例来介绍fisrt_value、last_value、通过计算各个部门的工资合计以及各个部门合计工资占总工资的比例小案例来介绍如何计算百分比及ratio_to_report分析函数的使用***
【SQL开发实战技巧】系列(十五):查找最值所在行数据信息及快速计算总和百之max/min() keep() over()、fisrt_value、last_value、ratio_to_report
|
机器学习/深度学习 存储 SQL
别再用 offset 和 limit 分页了,性能太差
别再用 offset 和 limit 分页了,性能太差
1507 0
别再用 offset 和 limit 分页了,性能太差
|
SQL 关系型数据库 MySQL
十一、操作delete或者update语句,加个limit或者循环分批次删除
十一、操作delete或者update语句,加个limit或者循环分批次删除
313 0
|
SQL 关系型数据库 MySQL
为啥count(*)会这么慢?
本没想着写这篇文章的,因为我觉得这个东西大多数有经验的开发遇到过,肯定也了解过相关的原因,但最近我看到有几个关注的技术公众号在推送相关的文章。实在令我吃惊!
147 0
|
算法
【刷算法】包含min函数的栈
【刷算法】包含min函数的栈
|
SQL 存储 关系型数据库
我说 SELECT COUNT(*) 查询很慢,面试官让我回去等通知
我说 SELECT COUNT(*) 查询很慢,面试官让我回去等通知