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

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


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

相关推荐
天远API8 分钟前
零信任架构实战:基于天远股权穿透构建自动化供应链授信穿透网关
java·人工智能·架构·自动化
最强小杰20 分钟前
claude-opus-5.5 API 接入教程:anthropic-version 头、tools 定义、流式事件格式三个常见问题全部梳理,收藏备用
java·服务器·网络·ai
Cc.Y23 分钟前
Java零基础入门:方法(函数)深度掌握
java·开发语言
奋发向前wcx1 小时前
CSP-J复赛模拟赛1 王晨旭补题 2026.10.2
java·开发语言
卓怡学长1 小时前
w192基于springboot“考研情报站”微信小程序设计与实现
java·spring boot·spring·微信小程序·intellij-idea
周杰伦fans1 小时前
C#对话摘要降低Token消耗
人工智能·后端·c#
wuminyu1 小时前
JEP491中synchronized关键字引发的平台线程钉住解决方法简介
java·linux·c语言·jvm·c++
做系统的大强1 小时前
脚本说 PASS、OS 读全零 —— 到底是谁在撒谎?
后端·操作系统
rm -rf * && haha.sh2 小时前
【Linux】Rocky 9.8 操作系统磁盘分区与MySQL数据目录迁移
linux·运维·mysql
anew___2 小时前
《从零手写操作系统 (20):ELF加载器——让OS读懂现代编译器》
java·服务器·前端