MySQL目前常用的分页方式有两种:

1.利用limit实现分页,语法为“SELECT*FROM 表名 limit 开始记录数,每页条数”;

2.利用主键索引实现分页,语法为“SELECT*FROM 表名 WHERE 字段名 > (页数*10) LIMIT 条数”;

一般使用第一种方式居多,适用于数据量不大的场景:

SELECT * FROM user LIMIT 0,10;#0是开始的记录数,10是条数

如果换成第二种写法:

SELECT * FROM user WHERE id > (0*10) LIMIT 10

id是主键。如果是第X页共Y条:(X从0开始计算)

SELECT * FROM user WHERE id > (X*10) LIMIT Y

当然,这种写法存在一定问题,如果第0页的id=5的数据被删除了,就会导致查询第0页的数据和第1页的数据有重合,第0页是1-4,6-11(默认一页10条数据,因为limit 10,所以会查询到id=11),第二页就是11-20,可见id=11重合了。

PS:当然关于id不连续的问题,你可以“逻辑删除”,增加一个 is_del 的属性,当is_del=1时表明该数据“已经删除”。

但是对于大量数据来说,这种问题是可以忽视的。

主要的是,这种大量数据的表,是机会不会去对其进行删除甚至修改操作的。

那么为什么大量数据使用第二种更合理呢?我们使用MySQL的关键字explain来看一下大概。

EXPLAIN SELECT * FROM test LIMIT 0,10

其他参数不做说明,就看type:扫描类型

效率从最好到最坏依次是:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

可见limit的分页效果最差。

当然,实际当中你可以增加一些查询参数,让查询不再走全表扫描。

做对比的话就不增加查询条件了。

EXPLAIN SELECT * FROM test WHERE id > (0*10) LIMIT 10

这个走的是range  范围查询  明显好多了。

但这实际上还是通过主键的查询,取巧了而已。并且主键有主键索引,查询更快。

一般情况下,得保证查询至少达到range级别,最好能达到ref。

第一种方式和第二种方式当数据量不多的时候,是第一种方式占优势,毕竟没有数据重合的问题,并且查询的速度也没有明显差别。但是当数据量上去了,差别就很明显了,

SELECT * FROM test LIMIT 0,10

SELECT * FROM test LIMIT 100,10

查询速度是不一样的 ,查询了第100页时,实际上前99页都已经被扫描过。

所以第二种的方式更适用于大量数据的场景

那么关于第一种,再多说一些。你可以通过增加一些查询参数去限制type,但是参数加的不好甚至还不如不加!牵涉到回表问题。

关于回表:

先了解一些B+树:

在这个树状结构里,我们需要关注的是,最下面一层节点,也就是 叶子结点 。而这个叶子结点里放的信息会根据当前的索引是 主键还是非主键 有所不同。

  • 如果是 主键索引 ,它的叶子节点会存放完整的行数据信息。

  • 如果是 非主键索引 ,那它的叶子节点则会存放主键,如果想获得行数据信息,则需要再跑到主键索引去拿一次数据,这叫 回表 。(实际上查询了两次表)

比如执行:

select * from page where user_name = "小白10";

会通过非主键索引去查询 user_name 为" 小白10 "的数据,然后在叶子结点里找到" 小白10 "的数据对应的 主键为10 。

此时回表到 主键索引 中做查询,最后定位到 主键为10的行数据 。

但不管是主键还是非主键索引,他们的叶子结点数据都是 有序的 。比如在主键索引中,这些数据是根据主键id的大小,从小到大,进行排序的。

所以说,如果你加的查询参数是无索引,是无序的,那么就是“不如不加”。

那么第一种如何的去优化呢?

上面select后面带的是 星号 *,也就是要求获得行

select * from page  where id >=(6000000) order by id limit 10;

数据的 所有字段信息。

我们结合第二种可以得到一种优化,比如执行的是:

select * from page order by id limit 6000000, 10;

由于这次的offset=6000000,会在innodb里的主键索引中获取到第0到(6000000 + 10)条 完整行数据,从引擎层获取到 很多无用的数据 ,而获取的这些无用数据都是要耗时的。

当select后面是*号时,就需要拷贝完整的行信息, 拷贝完整数据 跟 只拷贝行数据里的其中一两个列字段 耗时是不同的,这就让原本就耗时的操作变得更加离谱。

因为前面的offset条数据最后都是不要的,就算将完整字段都拷贝来了又有什么用呢,所以我们可以将sql语句修改成下面这样:

select * from page  where id >=(select id from page  order by id limit 6000000, 1) order by id limit 10;

上面这条sql语句,里面先执行子查询 select id from page order by id limit 6000000, 1 , 这个操作,其实也是将在innodb中的主键索引中获取到 6000000+1 条数据,然后server层会抛弃前6000000条,只保留最后一条数据的id。

但不同的地方在于,在返回server层的过程中,只会拷贝数据行内的id这一列,而不会拷贝数据行的所有列,当数据量较大时,这部分的耗时还是比较明显的。

在拿到了上面的id之后,假设这个id正好等于6000000,那sql就变成了

select * from page  where id >=(6000000) order by id limit 10;

这样innodb再走一次 主键索引 ,通过B+树快速定位到id=6000000的行数据,时间复杂度是lg(n),然后向后取10条数据。

这样性能确实是提升了,亲测能快一倍左右,属于那种耗时从3s变成1.5s的操作。

这······

属实有些杯水车薪,有点搓,属于没办法中的办法。

基于非主键索引的limit执行过程

上面提到的是主键索引的执行过程,我们再来看下基于 非主键索引 的limit执行过程。

比如下面的sql语句

select * from page order by user_name  limit 0, 10;

server层会调用innodb的接口,在innodb里的非主键索引中获取到第0条数据对应的主键id后, 回表 到主键索引中找到对应的完整行数据,然后返回给server层,server层将其放到结果集中,返回给客户端。

而当offset>0时,且offset的值较小时,逻辑也类似,区别在于,offset>0时会丢弃前面的offset条数据。

也就是说非主键索引的limit过程,比主键索引的limit过程,多了个回表的消耗。

但当offset变得非常大时,比如600万,此时执行explain。

非主键索引offset值超大时走全表扫描

可以看到type那一栏显示的是ALL,也就是 全表扫描 。

这是因为server层的 优化器 ,会在执行器执行sql语句前,判断下哪种执行计划的代价更小。

很明显,优化器在看到非主键索引的600w次回表之后,摇了摇头,还不如全表一条条记录去判断算了,于是选择了全表扫描。

因此,当limit offset过大时,非主键索引查询非常容易变成全表扫描。是真·性能杀手。

这种情况也能通过一些方式去优化。比如

select * from page t1, (select id from page order by user_name limit 6000000, 100) t2  WHERE t1.id = t2.id;
通过 select id from page order by user_name limit 6000000, 100 。先走innodb层的user_name非主键索引取出id,因为只拿主键id, 不需要回表 ,所以这块性能会稍微快点,在返回server层之后,同样抛弃前600w条数据,保留最后的100个id。然后再用这100个id去跟t1表做id匹配,此时走的是主键索引,将匹配到的100条行数据返回。这样就绕开了之前的600w条数据的回表。当然,跟上面的case一样,还是没有解决要白拿600w条数据然后抛弃的问题,这也是非常挫的优化。像这种,当offset变得超大时,比如到了百万千万的量级,问题就突然变得严肃了。这里就产生了个专门的术语,叫 深度分页 。

深度分页问题
深度分页问题,是个很恶心的问题,恶心就恶心在,这个问题,它其实 无解 。

不管你是用mysql还是es,你都只能通过一些手段去"减缓"问题的严重性。

遇到这个问题,我们就该回过头来想想。

为什么我们的代码会产生深度分页问题?

它背后的原始需求是什么,我们可以根据这个做一些规避。

如果你是想取出全表的数据

有些需求是这样的,我们有一张数据库表,但我们希望将这个数据库表里的所有数据取出,异构到es,或者hive里,这时候如果直接执行

select * from page;

这个sql一执行,狗看了都摇头。

因为数据量较大,mysql根本没办法一次性获取到全部数据,妥妥 超时报错 。

于是不少mysql小白会通过 limit offset size 分页的形式去分批获取,刚开始都是好的,等慢慢地,哪天数据表变得奇大无比,就有可能出现前面提到的 深度分页 问题。

这种场景是最好解决的。

我们可以将所有的数据 根据id主键进行排序 ,然后分批次取,将当前批次的最大id作为下次筛选的条件进行查询。

可以看下伪代码

batch获取数据

这个操作,可以通过主键索引,每次定位到id在哪,然后往后遍历100个数据,这样不管是多少万的数据,查询性能都很稳定。

batch分批获取user表

如果是给用户做分页展示

如果深度分页背后的原始需求只是产品经理希望做一个展示页的功能,比如商品展示页,那么我们就应该好好跟产品经理battle一下了。

什么样的翻页,需要翻到10多万以后,这明显是不合理的需求。

是不是可以改一下需求,让它更接近用户的使用行为?

比如,我们在使用谷歌搜索时看到的翻页功能。

一般来说,谷歌搜索基本上都在20页以内,作为一个用户,我就很少会翻到第10页之后。

作为参考。

如果我们要做搜索或筛选类的页面的话,就别用mysql了,用es,并且也需要控制展示的结果数,比如一万以内,这样不至于让分页过深。

如果因为各种原因,必须使用mysql。那同样,也需要控制下返回结果数量,比如数量1k以内。

这样就能勉强支持各种翻页,跳页(比如突然跳到第6页然后再跳到第106页)。

但如果能从产品的形式上就做成不支持跳页会更好,比如 只支持上一页或下一页 。

上下页的形式

这样我们就可以使用上面提到的start_id方式,采用分批获取,每批数据以start_id为起始位置。这个解法最大的好处是不管翻到多少页,查询速度永远稳定。

听起来很挫?

怎么会呢,把这个功能包装一下。

变成像抖音那样只能上划或下划,专业点,叫 瀑布流 。

是不是就不挫了?

总结
limit offset, size 比 limit size 要慢,且offset的值越大,sql的执行速度越慢。

当offset过大,会引发 深度分页 问题,目前不管是mysql还是es都没有很好的方法去解决这个问题。只能通过限制查询数量或分批获取的方式进行规避。

遇到深度分页的问题,多思考其原始需求,大部分时候是不应该出现深度分页的场景的,必要时多去影响产品经理。

如果数据量很少,比如1k的量级,且长期不太可能有巨大的增长,还是用 limit offset, size 的方案吧,整挺好,能用就行。

文末感谢:互联网公司都怎么实现分页的,拿 MySQL 使劲Limit?_java_beautiful的博客-CSDN博客

受益匪浅,告辞!

MySQL的分页你还在使劲的limit?相关推荐

  1. MySQL 数据库 分页查询/聚合查询

    引言 在本篇博客简单介绍一下分页查询以及聚合查询简单操作. 分页查询 在MySQL中,分页查询一般都是使用limit子句实现,limit子句声明如下: SELECT * FROM table LIMI ...

  2. mysql bean分页查询_javabean 来实现 MySQL 的分页

    javabean 来实现 MySQL 的分页.今天写了个 mysql 分页的 javabean,是用 mysql 里的 limit 来实现的. sql = "select * from te ...

  3. mysql中如何分页查询_MySQL_mysql分页原理和高效率的mysql分页查询语句,以前我在mysql中分页都是用的 l - phpStudy...

    mysql分页原理和高效率的mysql分页查询语句 以前我在mysql中分页都是用的 limit 100000,20这样的方式,我相信你也是吧,但是要提高效率,让分页的代码效率更高一些,更快一些,那我 ...

  4. Mysql海量数据分页查询优化

    查看代码打印1 SELECT * FROM table ORDER BY id LIMIT 1000,10; 以上SQL语句在原理上和在实际操作中是不会存在什么问题,但是当table表的数据量达到几十 ...

  5. Mysql索引介绍及使用注意事项,limit分页查询,慢查询分析

    Mysql索引介绍及使用注意事项,limit分页查询,慢查询分析 本文将从以下十二个方面进行介绍: 一.索引概念介绍 二.索引类型FULLTEXT,HASH,BTREE,RTREE有什么功能和性能上的 ...

  6. 【mySQL】mysql数据库分页查询讨论专题

    目录 一.limit分页公式.总页数公式 1 limit分页公式 2 总页数公式 二 .Mysql的三种分页方法 1 limit m,n分页语句(低效) 2 limit m语句 (有局限) 三. 查询 ...

  7. Mysql中分页查询两个方法比较

    mysql中分页查询有两种方式, 一种是使用COUNT(*)的方式,具体代码如下 1 2 3 SELECT COUNT(*) FROM foo WHERE b = 1; SELECT a FROM f ...

  8. 实战!聊聊如何解决MySQL深分页问题

    前言 我们日常做分页需求时,一般会用limit实现,但是当偏移量特别大的时候,查询效率就变得低下.本文将分四个方案,讨论如何优化MySQL百万数据的深分页问题,并附上最近优化生产慢SQL的实战案例. ...

  9. MySQL高效分页解决方案集

    2019独角兽企业重金招聘Python工程师标准>>> 很久以前的一次面试中,被面试官问到这个问题,由于平时用到的分页方法不多,只从索引.分表.使用子查询精准定位偏移以外,没有使用到 ...

最新文章

  1. C++ 各种构造函数
  2. 51 nod 1495 中国好区间 奇葩卡时间题 700ms 卡O(n*log(n)), 思路:O(n)尺取法
  3. vue项目编写html,从头搭建、编写一个VUE项目
  4. vue人员轨迹_Vue项目(vuecli3.0搭建)集成高德地图实现路线轨迹绘制
  5. 在ASP.NET MVC中使用Knockout实践07,自定义验证信息的位置与内容
  6. 不谈商业模式,为什么众筹新闻难成功
  7. Lenovo联想键盘关闭fn功能恢复F1-F12按键
  8. 第三代oid铺码软件_你好点读笔!自制小达人点读目录册之书名贴铺码
  9. 电力系统中的Kron简化,含MATLAB代码(全网唯一)
  10. python爬虫-抓取内涵吧内涵段子
  11. 2023跨境出海指南:韩国网红营销白皮书
  12. OCP、Avalon、Wishbone、IBM Core Connect
  13. 【PAT算法之路】 -- 专栏总揽
  14. 快手二面:a==1 a==2 a==3 是 true 还是 false?
  15. 华为OD机试真题2023(JavaScript)
  16. 用仿ActionScript的语法来编写html5——第六篇,TextField与输入框
  17. 深度Linux Deepin系统安装教程使用体验
  18. 诉不尽女儿心事——走进古代女性的闺房(组图)
  19. (编曲乐理)用理性思维学乐理(一)—入门基础乐理
  20. Wsyscheck20080122(V1.68.21)

热门文章

  1. mysql like_mysql 模糊查询 like 语句
  2. 【原创】ES5高效封装WIN10系统教程2020系列(二)准备封装环境
  3. 【Mybatis+spring整合源码探秘】--- mybatis整合spring事务原理
  4. Endnote——论文写作与文献管理神器
  5. 破解YourKit Java Profiler
  6. 【AFO】此是终点 亦是梦的起点
  7. Path、Paths、Files的使用
  8. 提高Tomcat并发量的几种方法
  9. python加注释的快捷键_详析python多行代码注释快捷键的用法
  10. datagrid技巧大全