MySQL 面试题——深度分页优化

目录

1.问题

MySQL 的深度分页(如 LIMIT 1000000, 20)会产生以下严重问题:

  • 性能问题
    • 要扫描前 N 条记录:即使只取 20 条,MySQL 也必须读取前 1000000 条记录并丢弃
    • 随着页码增大性能急剧下降:第 1 页很快,第 10 万页很慢
  • 内存压力
    • 大量非目标数据进入内存:扫描过程中加载大量索引页和数据页
    • 热点数据被挤出:频繁访问的数据页可能被这些扫描数据替换出缓冲池
    • 内存命中率下降:导致其他查询性能也受影响
  • I/O 开销
    • 回表操作:每条索引记录都需要随机读取数据页
    • 大量随机读取:前 100 万条记录的索引扫描虽然顺序,但回表是随机 I/O
  • 网络和连接问题
    • 大量无用数据传输:虽然最终只返回 20 条,但内部处理过程中有大量数据传输
    • 网络延迟累积:特别是在分布式环境中,内部数据传输消耗大量网络资源
    • 执行时间过长:可能超过 wait_timeoutinteractive_timeout 设置
    • 连接池耗尽:慢查询占用连接,影响其他业务

2.优化方案

2.1.游标分页

(1)游标分页是一种基于指针 (Cursor) 的分页方式,它通过记住上一页最后一条数据的位置,来获取下一页数据。游标分页对索引的核心限制是:游标字段必须是排序字段的一部分,且索引设计必须同时满足过滤条件和排序条件。其工作原理如下:

  • 不依赖页码,而是依赖上一次查询的最后一条记录
  • 使用 WHERE 排序字段 > 上一页最大值的方式获取下一页
  • 类似"加载更多"而不是"跳转到第 N 页"
sql 复制代码
-- 下一页
SELECT * FROM orders 
WHERE id > last_id 
ORDER BY id 
LIMIT 20;

同理,查询上一页的 SQL 语句如下:

sql 复制代码
-- 上一页 
SELECT * FROM orders 
WHERE id < first_id 
ORDER BY id DESC  -- 注意排序方向相反
LIMIT 20;
-- 然后在应用层反转数组

(2)一般来说,客户端的请求参数与服务器端的响应格式可设计如下:

java 复制代码
请求参数:
- limit: 每页数量
- cursor: 游标(可选,第一页不传)
- direction: 'next' 或 'prev'(可选,默认next)

响应格式:
{
  "data": [...],
  "prevCursor": "xxx",  // 用于上一页
  "nextCursor": "yyy",  // 用于下一页
  "hasPrev": true,
  "hasMore": true
}

2.2.使用子查询进行延迟关联

子查询只覆盖索引扫描,不需要回表,大大减少了 I/O 开销。具体对比如下:

sql 复制代码
-- 直接查询
SELECT * FROM orders ORDER BY create_time LIMIT 100000, 10;

直接查询的执行流程:

  • 扫描二级索引 (create_time) 找到前 100010 条记录的主键 ID
  • 对每个 ID 进行回表(100010 次回表)
  • 获取完整数据行
  • 排序后返回最后 10 条
  • 内存负担:100010 条完整数据行都加载到内存
sql 复制代码
-- 优化后:先查主键,再关联
SELECT o.* 
FROM orders o
INNER JOIN (
    SELECT id 
    FROM orders 
    ORDER BY create_time 
    LIMIT 100000, 10
) tmp ON o.id = tmp.id;

执行流程:

  • 子查询只扫描二级索引 (create_time) 获取 100010 个 ID(无需回表)
  • 子查询的 SELECT id 可以使用覆盖索引,所有数据都在索引页中
  • 子查询返回 10 个 ID (LIMIT 100000, 10)
    -主查询用这 10 个 ID进行精准回表(仅 10 次回表)
  • 内存负担:只加载 10 条完整数据行

2.3.覆盖索引

(1)设置覆盖索引的核心思路就是把查询需要用到的字段都放到同一个索引中去,这样查询所需的全部数据都在索引中,从而不需要进行回表操作。

sql 复制代码
-- 创建覆盖索引
ALTER TABLE orders ADD INDEX idx_cover_page (id, order_no, amount);

-- 使用了覆盖索引的 SQL 语句
SELECT id, order_no, amount FROM orders ORDER BY id LIMIT 1000000, 20;

(2)使用 EXPLAIN 验证是否生效

sql 复制代码
EXPLAIN SELECT id, order_no, amount FROM orders ORDER BY id LIMIT 1000000, 20;

关键看 Extra 字段:

  • Using index:表示使用了覆盖索引,查询完全在索引中完成,无需回表
  • Using where、Using filesort:表示没有用到索引或需要额外排序

(3)注意事项:

  • 只查询部分字段:不能是 SELECT *,必须明确指定字段列表
  • 字段不要太多:索引字段过多会导致索引臃肿,影响写入性能
  • B-Tree 索引才能覆盖:哈希索引、全文索引不支持覆盖索引

有关覆盖索引的相关知识可以查看 MySQL 高级篇知识点------索引优化与查询优化这篇文章中的第 8 节。

2.4.限制最大页数

在产品层面限制用户只能查看前 N 页,比如最多 100 页,超过时提示用户缩小搜索范围。

相关推荐
2301_803875616 小时前
PHP 中处理会话数组时的类型错误解析与修复指南
jvm·数据库·python
m0_743623926 小时前
c++如何批量修改文件后缀名_std--filesystem--replace_extension【实战】
jvm·数据库·python
2501_914245937 小时前
CSS如何处理CSS变量作用域冲突_利用特定类名重写变量值
jvm·数据库·python
maqr_1108 小时前
MySQL数据库迁移到云端如何保障安全_数据加密与SSL连接配置
jvm·数据库·python
u0109147608 小时前
MySQL如何限制触发器递归调用的深度_防止触发器死循环方法
jvm·数据库·python
weixin_381288188 小时前
MySQL中如何使用HEX函数转换十六进制_MySQL进制转换函数
jvm·数据库·python
Deitymoon9 小时前
嵌入式数据库——SQLite基础
数据库·sqlite
YMatrix 官方技术社区9 小时前
美国·硅谷|YMatrix 即将亮相 Postgres Conference 2026,前瞻 AI 时代的数据基座
数据库·数据仓库·postgresql·时序数据库·ymatrix
bKYP953cL9 小时前
构建自己的AI编程助手:基于RAG的上下文感知实现方案
数据库·人工智能·ai编程
Bert.Cai9 小时前
MySQL DML简介
数据库·mysql