✨字节二面✨MySQL深分页如何优化

故事很长,但不难讲,脸红相遇,眼红收场


前言

前同事刚参加完字节的二面,向我反馈了一道MySQL 深分页的优化 题目,起初我以为这只是一道很常规的深分页的题目,但是听完字节面试官的追问,才发现 水很深

参考资料

正文

一. 题目说明

优化 如下SQL 语句,其中tb 表的b列存在索引。

sql 复制代码
SELECT * FROM tb WHERE tb.b = 5 LIMIT 30000,5;

前同事给出的答案如下。

sql 复制代码
SELECT * FROM tb WHERE id IN (SELECT id FROM tb WHERE tb.b = 5 LIMIT 30000,5);

此时面试官进行了两个问题追加。

  1. 优化方案的SQL语句能真实执行吗;
  2. 还有其它SQL优化手段吗。

前同事一个没答上来,随即面试官又追加了一个问题。

  1. 请说一下一开始给到你的SQL语句为什么会慢。

那么到这里,可以发现字节面试官其实真正的目的是想问为什么深分页会慢,这个问题你有仔细想过吗,如果问到你,你会怎么回答。

二. 深分页演示

创建如下一张表。

sql 复制代码
CREATE TABLE blog_post (
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
    uid VARCHAR(255) NOT NULL COMMENT '唯一标识',
    like_count INT NOT NULL DEFAULT '0' COMMENT '点赞统计',
    collect_count INT NOT NULL DEFAULT '0' COMMENT '收藏统计',
    recommend TINYINT(1) NOT NULL DEFAULT '0' COMMENT '推荐标识',
    KEY like_count_index(like_count)
);

向表中插入100W条数据,统计如下。

txt 复制代码
+----------+
| COUNT(*) |
+----------+
|  1100010 |
+----------+

执行如下一条深分页的查询SQL

sql 复制代码
SELECT 
  * 
FROM
  blog_post
WHERE
  like_count > 0
LIMIT 500000,10;

耗时如下。

txt 复制代码
10 rows in set (1.34 sec)

很简单的一条查询语句结果耗时达到了1.34秒。

三. 深分页为什么慢

还是以如下这条语句来分析。

sql 复制代码
SELECT 
  * 
FROM
  blog_post
WHERE
  like_count > 0
LIMIT 500000,10;

上述这条SQL 语句在执行时,MySQLServer 层和InnoDB 存储引擎层存在如下这样的交互

通过上图得到如下两个信息。

  1. 存在大量回表操作 。因为不满足覆盖索引,所以需要先通过二级索引获取到主键Id ,然后回表从主键索引中获取到完整记录,而回表操作是随机IO,会存在大量耗时的情况;
  2. limit 条件判断发生在 Server Server 层准备将数据返回给客户端时,才会去判断limit的条件。

现在就可以回答为什么深分页慢了,首先慢的深分页通常没有使用到覆盖索引,此时为了得到完整记录就需要回表,而回表是随机磁盘IO ,速度慢消耗大,这就是深分页慢的 根本原因 。其次深分页场景里limit 的偏移量通常很大,并且limit 的判断发生在Server 层,这就导致存在大量无效的回表 ,即前50W 条记录都是通过二级索引再回表到主键索引得到的,但实际这50W 条件记录都不是被需要的,做的这50W次回表都是无意义的。

总结下来就是。

  1. 没有使用覆盖索引存在大量回表操作
  2. limit 的判断发生在 Server 层导致大量回表操作都是无意义的

三. 优化深分页

既然明确了深分页为什么慢 ,那么现在就可以很好的针对原因从SQL层面进行优化。

既然 根本原因 是存在大量无效回表,那么我们可以着手消灭 掉回表操作或者减少回表操作。

首先可以将SQL优化如下。

sql 复制代码
SELECT 
  like_count 
FROM
  blog_post
WHERE
  like_count > 0
LIMIT 500000,10;

将查询字段修改为like_count,此时深分页就算再深,不用回表,速度也是十分可观的,此时查询耗时如下。

txt 复制代码
10 rows in set (0.13 sec)

速度提升十分可观,但假如一定要查询完整记录呢,此时可以将SQL优化如下。

sql 复制代码
SELECT 
  *
FROM
  blog_post
INNER JOIN
  (SELECT id FROM blog_post WHERE like_count > 0 LIMIT 500000,10) AS bpid
ON
  blog_post.id = bpid.id;

上述语句的查询耗时如下。

txt 复制代码
10 rows in set (0.17 sec)

速度提升也是十分可观,但其实无论哪种优化方式,从SQL 层面入手的话,思路都是将回表操作 进行了减少 甚至消除

总结

深分页 其实就是使用limit 时偏移量很大,而被偏移的数据其实都是不被需要 的,如果在获取这些数据时耗时很高,那么这些很高的耗时是白白浪费 的,那么什么情况下会出现获取这些数据耗时很高呢,最常见的就是没有使用到覆盖索引的情况,此时每一条被偏移的数据都要回表一次才能得到完整数据,耗时自然就很高了。

所以如果一定要深分页,最好的优化方案就是避免出现回表,也就是利用覆盖索引


总结不易,如果本文对你有帮助,烦请点赞,收藏加关注,谢谢帅气漂亮的你。

相关推荐
qw94936 分钟前
MySQL 08 章——聚合函数
数据库·mysql
Q_192849990636 分钟前
基于Spring Boot的便民医疗服务小程序
spring boot·后端·小程序
开心工作室_kaic2 小时前
springboot548二手物品交易boot代码(论文+源码)_kaic
前端·数据库·vue.js·后端·html5
Java知识日历3 小时前
【内含例子代码】Spring框架的设计模式应用(第二集)
java·开发语言·后端·spring·设计模式
尘浮生5 小时前
Java项目实战II基于微信小程序的家庭大厨(开发文档+数据库+源码)
java·开发语言·数据库·微信小程序·小程序·maven
Java知识技术分享5 小时前
spring boot通过文件配置yaml里面的属性
java·spring boot·后端
Demons_kirit5 小时前
Spring Boot + Redis + Sa-Token
spring boot·redis·后端
一休哥助手5 小时前
深入解析Spring Boot项目的类加载与启动流程
java·spring boot·后端
丁总学Java5 小时前
定义一个名为 MyCache 的 Spring 配置类
java·spring