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 页,超过时提示用户缩小搜索范围。

相关推荐
散装DBA2 小时前
OpenClaw+钉钉机器人实现数据库操作
数据库·机器人·钉钉
哈库纳玛塔塔2 小时前
公元前日期处理的两种方案
数据库·算法·mybatis
XLYcmy2 小时前
智能体大赛 核心功能 可信文献检索与系统性知识梳理
数据库·ai·llm·prompt·知识图谱·agent·检索
逻辑君3 小时前
如何在PostgreSQL里删除和增加数据库
数据库·postgresql
XP62263 小时前
MySQL 数据库连接数查询、配置
数据库·mysql
之歆3 小时前
MySQL 主从复制完全指南
android·mysql·adb
砚边数影3 小时前
智慧校园后端演进:如何处理每日亿级传感器数据的“存、压、查”?
java·数据库·时序数据库·kingbase·数据库平替用金仓·金仓数据库
KG_LLM图谱增强大模型4 小时前
SCHEMA-MINERpro:基于智能体AI的本体映射框架——在人机协作工作流中通过大模型发现科学新模式
数据库·人工智能
zsyf19874 小时前
MySQL如何执行.sql 文件:详细教学指南
数据库·mysql