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. Nuxt.js 应用中的 schema:written 事件钩子详解

    title: Nuxt.js 应用中的 schema:written 事件钩子详解 date: 2024/11/15 updated: 2024/11/15 author: cmdragon exce ...

  2. Javascript遍历目录时使用for..in循环无法获取Files对象和SubFolders对象问题的解决方法

    1 Javascript遍历目录时使用for..in循环无法获取Files对象和SubFolders对象 1.1 问题场景   在JavaScript中遍历目录,使用for.. in循环时,无法获取到 ...

  3. 记ios的input框获取焦点之后界面放大问题

    在移动端开发项目中,发现页面在使用 iPhone 访问的时候,点击 input 和 textarea 等文本输入框聚焦 focus() 时,页面会整体放大,而且失去焦点之后页面不能返回原来的样子.检查 ...

  4. Alain 配置管理

    app/assets/tmp/app-data.json 中保存了默认的一些应用配置 app 应用配置 user 默认用户配置 menu 菜单配置信息 在 App.Module 中,使用 APP_IN ...

  5. vba interpreter 结束

    https://github.com/inshua/vba-interpreter 已覆盖几乎 VB 所有的特性,只是库还不够全. VB 语言自身较为落后,语法也有诸多设计不当.最严重的莫过于函数和数 ...

  6. 【Python】【爬虫】【问题解决方案记录】调试输出存在数据,print在控制台确丢失数据

    如下图,调试可以看到数据是完整的 但是print输出的,恰好丢失了中间的一大堆数据.对,下图打问号的地方应该是小说才对. 看代码可能看不出缺失内容,可视化看看 对吧,刚好缺失了小说. 后来我尝试用写文 ...

  7. Docker 容器运行一个 web 应用

    Docker 容器安装和基础使用请看上一篇 Docker 容器运行一个 web 应用 使用 docker 构建一个 web 应用程序. docker pull training/webapp # 载入 ...

  8. 祝大家这周圣诞快乐!!本周进军多模态!😀From LLMs to MLLMs:😜Exploring the Landscape of Multimodal Jailbreaking

    从LLMs到MLLMs:探索多模态越狱攻击的前景 禁止盗用,侵权必究!!!欢迎大家积极举报 ①脆弱性代表:越狱攻击(恶意指令/训练&解码干预). ②最近的越狱攻击: 整体说:构建越来越复杂场景 ...

  9. Spring Security默认登录页面代码位于哪里?

    问:Spring Security默认登录页面代码位于哪里? 答:它是从此类生成的org.springframework.security.web.authentication.ui.DefaultL ...

  10. [转]C#从MySQL数据库中读取

    实现了数据库的建表.存储数据的功能后,还需要实现数据库的读取,综合查资料后发现有两种发发比较好; 一.如需要界面操作,需要将数据表格在界面上显示出来的话,需要使用DataGrid控件. 基本操作流程: ...