MySQL分页性能思考

关键词:深度分页


背景

最近有一个需求:在后台管理页面中,需要展示产品信息的列表。

之前版本开发中产品信息是用户填写完所有字段之后能进行保存。在之前的基础上需要支持用户不完全填写字段进行展示和保存的功能。

一个很简单的想法是为空也直接保存就可以了,但是由于之前的开发中MySQL中已经约束了字段NOT NULL,担心修改表结构引发一些意料之外的错误。

因此考虑使用Redis作为临时保存信息的能力,避免直接修改MySQL的数据表格式,同时一般设置来说Redis也只会丢失1s的数据,可以接受这个丢失。

使用Redis之后,如何在列表页展示产品信息呢?这个简单问题引发了我的一些思考。

mysql怎么做分页,有哪几种方案

简单和通用的做法:offset+limit,sql语句大概是:

select * from my_table where my_table.name = 'oneName' order by my_table.id limit 100000,10;

对于offset+limit的方案,其会在offset及大的时候性能变慢:10w的offset:0.742s,0的offset为0.006s。

性能低的原因在于:大量无意义的回表查询(回表总次数为offset+limit的值),丢弃了大量数据(offset),只保留了很少部分(limit) 。like:

  1. 通过普通二级索引树idx_update_time,过滤update_time条件,找到满足条件的记录ID。
  2. 通过ID,回到主键索引树,找到满足记录的行,然后取出展示的列(回表
  3. 扫描满足条件的100010行,然后扔掉前100000行,返回。

其执行计划:

因此我们的优化思路在于:减少无意义的回表查询

有三种方法:

  1. 使用子查询提前筛选出id,避免无意义的回表和筛选。在下面的例子中,就使用子查询先筛选出a.id​,避免大量回表。耗时约为0.038,由于有了子查询,耗时肯定是比offset为0要高的。语句like:

    select id,name,balance FROM account where id >= (select a.id from account a where a.update_time >= '2020-09-19' limit 100000, 1) LIMIT 10;(可以加下时间条件到外面的主查询)
  2. 使用inner join​同一张表,原理其实和子查询是一样的。语句like:

    SELECT  acct1.id,acct1.name,acct1.balance FROM account acct1 INNER JOIN (SELECT a.id FROM account a WHERE a.update_time >= '2020-09-19' ORDER BY a.update_time LIMIT 100000, 10) AS  acct2 on acct1.id= acct2.id;

容易混淆:上面两个语句都用到了SELECT a.id FROM account a WHERE a.update_time >= '2020-09-19' ORDER BY a.update_time LIMIT 100000, 10​,但是需要注意,查询的是a.id​,走的索引,并不会回表哦。

  1. 游标法:使用主键id作为游标,同样可以避免大量无意义的回表,语句范例如下。

    select  id,name,balance FROM account where id > 100000 order by id limit 10;

值得一提的是,对于上面三种优化,都只提及了id作为筛选项目,如果涉及更多where条件之类的,优化思路都是类似的,即:减少无意义的回表查询。

MySQL和Redis一起之后如何分页

在使用REDIS保存用户没有完全填写的产品信息之后,列表展示页面我们需要同时展示REDIS中保存的不完全填写的数据和MySQL中完全填写的数据。

对于列表页这样瀑布流展示的数据,在展示的时候我们需要合并MySQL和REDIS中的数据,并且尽量保持原有逻辑,按照产品id降序排列,如何做呢?

补充:REDIS保存的数据是用的String类型,key就是产品的id,value为产品信息序列化后的字符串。又使用了一个Zset来保存Redis中保存的产品id。

在考虑混合Redis中的数据之后,相比于只用考虑MySQL的的数据,难点在于分页非常不好做,常规的limit+offset方案的性能让人无法接收!

为什么性能差

我们原有的查询中,一般没有考虑深度分页的问题,都是直接limit+offset进行查询。

在混合进Redis的数据之后,数据分布可能如下图所示:

数据在Redis中和MySQL中并没有一个固定的顺序,而是交错的,在这样的情况下暴力的limit+offset的方案就会导致更多的无效查询,比如:如果需要查询每页50条数据,当前处于第30页。如果只考虑MySQL的查询,只用limit 30*50,50​即可;现在同时考虑MySQL和Redis的数据查询,需要通过以下步骤:

  • 第一步需要首先通过limit 0,30*50+50​塞选出MySQL中的产品ID;
  • 第二步取出Redis的zset中保存的所有的产品ID;
  • 第三步在业务代码中按照顺序重新降序组装出本次展示所需要的产品ID;
  • 第四步并再回到MySQL和REIDS中查询并组装展示;

上面第一步中直接全部取出产品信息会进一步增加耗时,这里已经算是优化后的步骤了。

上面的步骤看起来非常的“没有必要”,我们逐个解释一下:

  • 第一步:第一步中为啥offset中要用30*50+50​取这么多产品ID,分明这次只需要50个数据?原因就在于混入Redis的数据之后,全局排名(偏移量)30*50​的数据,在MySQL中排名(偏移量)的范围是[0,30*50],所以必须全部取出参与排序。
  • 第二步:第二部中为啥要取出Redis中保存的所有的产品ID?原因在于Redis中zset虽然支持按照分数范围进行筛选,但是并不支持按照偏移量进行筛选,所以只能全部取出
  • 第三步,第四步:很正常的步骤。

怎么优化

性能这么差,简直不能忍受!所以我们上面MySQL做分页的优化可以排上用场了。MySQL深度分页的几个优化思路(子查询、inner join、游标)中游标优化可以解决我们的问题,子查询和inner joint并不能解决我们的问题。

因为使用游标优化之后,无论是MySQL中取数据还是Redis中取数据我们都可以直接用游标来限制取数据的范围了:MySQL中可以用where xxx>? limit 0,50​;Redis中zset中zrange​取数据的时候也可以限制范围了。而剩下的两种优化方法本质上我们举得案例中就已经采用了,对这种多数据源合并的情况虽然可能有一些提升,但并没有解决本质问题。 不过改用游标法之后,我们需要前端同学配合我们修改,将原先的offset+limit的方案迁移到游标方案来。

实际上Redis来保存仍然有一些点需要考虑,比如说随着产品ID增多,会产生zset的bigkey问题,或者是产品本身信息过多带来的bigkey问题等等。

此外,我们也可以考虑一些代码之外的方案,比如说和业务商量一下是否可以不保证按照产品ID递减的方式来展示,可以先展示完缓存的数据等等~

参考:

mysql查询 limit 1000,10 和limit 10 速度一样快吗?如果我要分页,我该怎么办?-腾讯云开发者社区-腾讯云

更推荐看这个 实战!聊聊如何解决MySQL深分页问题

MySQL分页性能思考的更多相关文章

  1. mysql 分页性能优化

    最简单的分页方法是这样的 , 该表中存在5w左右数据 执行时间平均在10s左右,因此该种方式在数据量大的情况下查询效率极低. 优化方式有以下几种 1.此种方式平均在7-8s之间(CreateDate ...

  2. mysql分页性能

    - select * from userinfo limit 20000,10  # 数据越往后越慢 - 索引表中扫: select * from userinfo where id in (sele ...

  3. MySQL— 索引,视图,触发器,函数,存储过程,执行计划,慢日志,分页性能

    一.索引,分页性能,执行计划,慢日志 (1)索引的种类,创建语句,名词补充(最左前缀匹配,覆盖索引,索引合并,局部索引等): import sys # http://www.cnblogs.com/w ...

  4. mysql 分页offset过大性能问题解决思路

    在公司干活一般使用sqlserver数据库.rownumber分页贼好用. 但是晚上下班搞自己的事情就不用sqlserver了.原因就是自己的渣渣1核2g的小服务器完全扛不住sqlserver那么大的 ...

  5. day05 mysql pymysql的使用 (前端+flask+pymysql的使用) 索引 解释执行 慢日志 分页性能方案

    day05 mysql pymysql   一.pymysql的操作     commit(): 在数据库里增删改的时候,必须要进行提交,否则插入的数据不生效       1.增, 删, 改  #co ...

  6. MySQL分页查询的性能优化

    MySQL limit分页查询的性能优化 Mysql的分页查询十分简单,但是当数据量大的时候一般的分页就吃不消了. 传统分页查询:SELECT c1,c2,cn… FROM table LIMIT n ...

  7. mysql数据库性能优化(包括SQL,表结构,索引,缓存)

    优化目标减少 IO 次数IO永远是数据库最容易瓶颈的地方,这是由数据库的职责所决定的,大部分数据库操作中超过90%的时间都是 IO 操作所占用的,减少 IO 次数是 SQL 优化中需要第一优先考虑,当 ...

  8. Mysql 分页语句Limit用法

    转载自:http://qimo601.iteye.com/blog/1634748 1.Mysql的limit用法 在我们使用查询语句的时候,经常要返回前几条或者中间某几行数据,这个时候怎么办呢?不用 ...

  9. 第6章 影响 MySQL Server 性能的相关因素

    前言: 大部分人都一致认为一个数据库应用系统(这里的数据库应用系统概指所有使用数据库的系统)的性能瓶颈最容易出现在数据的操作方面,而数据库应用系统的大部分数据操作都是通过数据库管理软件所提供的相关接口 ...

  10. MySQL分页优化中的“INNER JOIN方式优化分页算法”到底在什么情况下会生效?

    本文出处:http://www.cnblogs.com/wy123/p/7003157.html 最近无意间看到一个MySQL分页优化的测试案例,并没有非常具体地说明测试场景的情况下,给出了一种经典的 ...

随机推荐

  1. 洛谷P4913【深基16.例3】二叉树深度题解

    很简单的二叉树遍历问题,可以用dfs(深度优先搜索)解决. 看到数据范围,最大不超过 \(10^6\) ,可以不开 long long (但我还是习惯性地开了) 接下来上代码: #include< ...

  2. 痞子衡嵌入式:我在华邦电子&恩智浦2024联合技术论坛继续担任演讲嘉宾

    「华邦电子(Winbond)」是国际领先的存储器厂商,其串行 NOR Flash 产品在全球市场占有率稳居前列. 继去年华邦联合恩智浦成功搞了第一次技术论坛之后,今年华邦又联合意法半导体,恩智浦.莱迪 ...

  3. importlib 用法

    首先看一下importlib.import_module(name, package=None)函数的参数 函数调用存在两种方式: 1.绝对导入,name为完整路径str,package为None. ...

  4. Solr 的核心就是搜索

    原文  http://www.aptusource.org/2014/06/searching-is-what-its-all-about/ Solr 的主要功能就是强大的查询处理.在本文中,你将会看 ...

  5. 链路追踪之Jaeger

    官方地址:https://www.jaegertracing.io/ [安装] 官方提供了两个安装方式, 1. 基于二进制(https://www.jaegertracing.io/download/ ...

  6. vue中方法中数据已更新,但是视图却没有变化解决方法

    今天在项目中碰到这样一个问题: 从父组件中传过来的props中的数据,在子组件中想加入一个变量.在created中加入变量,在方法中打印次变量是有的,但是当变量发生变化之后,视图中是响应不到的. 解决 ...

  7. Mongodb4.4安装与使用

    MongoDB是一个高性能,开源,无模式的文档型数据库,是当前NoSql数据库中比较热门的一种.MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能 最丰富,最像关系数 ...

  8. Lua之基础篇

    新到一家公司,接触有些业务竟然直接通过服务器,在nginx层面就完成了,主要是基于OpenResty和Lua来实现的.打算深入了解一下这门神奇的语言... 为了嵌入应用程序中,从而为应用程序提供灵活的 ...

  9. 进程管理工具之supervisor(完整版)*

    Supervisor 介绍 Supervisor是用Python开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台daemon,并监控进程状态,异常退出时能自动重启.它是通过fork/ex ...

  10. 鸿蒙NEXT元服务:利用App Linking实现无缝跳转与二维码拉起

    [效果] 元服务链接格式(API>=12适用):https://hoas.drcn.agconnect.link/ggMRM 生成二维码后效果: ​ [参考网址] 使用App Linking实现 ...