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

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


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

相关推荐
智码看视界3 小时前
Day19 Spring Boot启动优化:启动时间从8秒降到2秒
java·spring boot·后端·启动优化
IT_陈寒4 小时前
React中useEffect的依赖项把我坑惨了
前端·人工智能·后端
北京阿法龙科技有限公司4 小时前
AR智能眼镜安防应用核心指标:识别距离筑牢防线
java·开发语言·ar
殘殤血4 小时前
Tomcat的事件监听机制:观察者模式 _
java·观察者模式·tomcat
汤姆yu4 小时前
面向具身智能的物理视频模拟器:蚂蚁灵波LingBot-Video开源模型全解析
java·大数据·人工智能·gpt·开源·大模型·音视频
SQL-First布道者4 小时前
Spring JDBC Ultra 十边型战士
java·spring boot·后端·mysql·spring·mybatis
殘殤血4 小时前
Tomcat的事件监听机制:观察者模式
java·观察者模式·tomcat
Dovis(誓平步青云)4 小时前
《精讲Spring Boot后端跑另一个端口:如何解决暴露服务器问题》
服务器·人工智能·spring boot·后端·生成对抗网络
Java面试题总结4 小时前
GatewayWorker 从零到一完整指南
java
没钥匙的锁14 小时前
04-Java面向对象基础
java·开发语言·mongodb