MySQL深度分页问题的三种解决方法
目录
- 前言
- 1. 子查询 + 覆盖索引(延迟关联)
- 2. 记录最大 ID(游标分页)
- 3. Elasticsearch 优化
- 其他优化思路
- 总结
- 扩展:mysql中 join、inner join、left join、right join区别
- 1. INNER JOIN(内连接)
- 2. LEFT JOIN(左外连接)
- 3. RIGHT JOIN(右外连接)
- 4. JOIN(默认是 INNER JOIN)
- 对比总结
- 关键注意事项
- 示例演示
- 查询结果对比
- 总结
前言
在 MySQL 中解决深度分页问题的核心思路是减少扫描的数据量,尤其是避免通过 LIMIT offset, size
导致的大范围数据扫描。以下是三种优化方法及其原理、适用场景和注意事项:
1. 子查询 + 覆盖索引(延迟关联)
原理
- 先通过覆盖索引(如二级索引
(name, id)
)快速定位目标页的起始id
,再通过主键索引回表查询数据。 - 子查询只需扫描二级索引,体积小且有序,能高效跳过
offset
行,获取起始id
。 - 主查询通过
id >= [子查询结果]
直接定位数据,避免全表扫描。
示例 SQL
SELECT * FROM mianshiya WHERE name = 'yupi' AND id >= ( SELECT id FROM mianshiya WHERE name = 'yupi' ORDER BY id LIMIT 99999990, 1 ) ORDER BY id LIMIT 10;
或使用 JOIN 优化:
SELECT * FROM mianshiya INNER JOIN ( SELECT id FROM mianshiya WHERE name = 'yupi' ORDER BY id LIMIT 99999990, 10 ) AS tmp ON mianshiya.id = tmp.id;
关键点
- 必须创建联合索引
(name, id)
,确保子查询直接利用索引有序性,避免临时排序(filesort)。 - 主查询的
name
条件可省略(若子查询结果id
对应的name
必为'yupi'
),但需权衡数据变更风险。
2. 记录最大 ID(游标分页)
原理
- 每次分页返回当前页的最大
id
,下页查询时通过WHERE id > max_id LIMIT size
跳过已读数据。 - 仅扫描目标数据(
size
行),时间复杂度稳定为O(size)
,性能极佳。
适用场景
- 连续分页(如“下一页”),不支持随机跳页。
- 数据按主键或有序字段分页(如
ORDER BY id
)。
示例 SQL
-- 第一页 SELECT * FROM mianshiya WHERE name = 'yupi' ORDER BY id LIMIT 10; -- 后续页(假设上一页最大 id 为 100) SELECT * FROM mianshiya WHERE name = 'yupi' AND id > 100 ORDER BY id LIMIT 10;
注意事项
- 若数据删除或新增可能导致少量重复或遗漏,需业务容忍。
- 需前端配合传递
max_id
,不可直接跳页。
3. Elasticsearch 优化
原理
- 将数据同步到 Elasticsearch,利用其分布式特性加速搜索。
- 通过
search_after
参数实现游标分页,类似记录max_id
方法,避免深分页性能问题。
ES 分页示例
{ "query": { "match": { "name": "yupi" } }, "sort": [{ "id": "asc" }], "size": 10, "search_after": [100] -- 上一页最后一条记录的排序值 }
ES 注意事项
- 默认限制
from + size <= 10000
,深度分页需改用search_after
或scroll
。 search_after
需基于唯一排序字段,且仅支持连续分页。
其他优化思路
- 业务限制:禁止深度跳页(如最多展示前 100 页)。
- 预计算分页位置:按时间或分段存储分页起始 ID(如每 1000 页记录一个起始 ID)。
- 分区表:按时间或范围分区,减少单次扫描数据量。
总结
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
子查询 + 覆盖索引 | 支持跳页,通用性强 | 依赖联合索引设计 | 需频繁分页且允许跳页 |
记录最大 ID | 性能最优,复杂度低 | 仅支持连续分页 | 连续分页(如瀑布流) |
Elasticsearch | 适合复杂搜索,分布式性能好 | 维护成本高,ES 需额外学习 | 高并发搜索+分页场景 |
核心要点
- 索引设计是优化基础,确保覆盖索引和排序字段匹配。
- 权衡业务需求,优先选择游标分页(记录
max_id
)或延迟关联(子查询)。 - 搜索引擎适用于复杂查询,但引入额外组件需评估成本。
扩展:MySQL中 join、inner join、left join、right join区别
在 MySQL 中,JOIN
操作用于将多个表中的数据关联起来。以下是 INNER JOIN
、LEFT JOIN
、RIGHT JOIN
的核心区别和用法总结,帮助你快速分清它们:
1. INNER JOIN(内连接)
作用:只返回两个表中完全匹配的行。
结果:仅包含两个表中都存在关联键值的行。
语法:
SELECT * FROM 表A INNER JOIN 表B ON 表A.键 = 表B.键;
示例:
- 表A(员工表)和表B(部门表)通过
department_id
关联。 - 结果:只显示有部门的员工信息,无部门或无员工的数据会被过python滤掉。
- 表A(员工表)和表B(部门表)通过
2. LEFT JOIN(左外连接)
作用:返回左表(
LEFT JOIN
左侧的表)的所有行,即使右表没有匹配。结果:左表所有数据 + 右表匹配的数据(无匹配时右表字段为
NULL
)。语法:
SELECT * FROM 表A LEFT JOIN 表B ON 表A.键 = 表B.键;
示例:
- 表A(员工表)LEFT JOIN 表B(部门表)。
- 结果:显示所有员工信息,即使员工没有部门(部门字段为
NULL
)。
3. RIGHT JOIN(右外连接)
作用:返回右表(
RIGHT JOIN
右侧的表)的所有行,即使左表没有匹配。结果:右表所有数据 + 左表匹配的数据(无匹配时左表字段为
NULL
)。语法:
SELECT * FROM 表A RIGHT JOIN 表B ON 表A.键 = 表B.键;
示例:
- 表A(员工表)RIGHT JOIN 表B(部门表)。
- 结果:显示所有部门信息,即使部门没有员工(员工字段为
NULL
)。
4. JOIN(默认是 INNER JOIN)
说明:在 MySQL 中,直接写 JOIN
等价于 INNER JOIN
。
SELECT * FROM 表A JOIN 表B ON 表A.键 = 表B.键; -- 等同于 INNER JOIN
对比总结
类型 | 行为 | 适用场景 |
---|---|---|
INNER JOIN | 仅返回两个表匹配的行 | 需要精确匹配的数据(如订单和商品) |
LEFT JOIN | 返回左表全部数据 + 右表匹配的数据(右表无匹配则为 NULL ) | 保留左表全部数据(如所有员工信息) |
RIGHT JOIN | 返回右表全部数据 + 左表匹配的数据(左表无匹配则为 NULL ) | 保留右表全部数据(如所有部门信息) |
关键注意事项
- 方向性:
LEFT JOIN
和RIGHT JOIN
的方向取决于表的书写顺序。LEFT JOIN
以左表为主,RIGHT JOIN
以右表为主。
- 过滤条件:
- 在
LEFT JOIN
中,若在WHERE
子句中对右表字段过滤(如WHERE 表B.键 IS NULL
),会筛选出仅存在于左表但右表无匹配的行。
- 在
- 性能:
INNER JOIpythonN
通常效率更高,因为它涉及的数据量更小。LEFT/RIGHT JOIN
可能因处理NULL
&nbjssp;值而略慢,尤其是在大表中。
示例演示
数据准备
-- 员工表(employees) +-------------+-------+---------------+ | employee_id | name | department_id | +-------------+-------+---------------+ | 1 | 张三 | 101 | | 2 | 李四 | 102 | | 3 | 王五 | NULL | +-------------+-------+---------------+ -- 部门表(departments) +---------------+-----------------+ | department_id | department_name | +---------------+-----------------+ | 101 | 技术部 | | 102 | 市场部 | | 103 | 财务部 | +---------------+-----------------+
查询结果对比
INNER JOIN(匹配数据):
SELECT * FROM employees INNER JOIN departments ON employees.department_id = departments.department_id;
结果:
| 1 | 张三 | 101 | 101 | 技术部 | | 2 | 李四 | 102 | 102 | 市场部 |
LEFT JOIN(保留所有员工):
SELECT * FROM employees LEFT JOIN departments ON employees.department_id = departments.department_id;
结果:
| 1 | 张三 | 101 | 101 | 技术部 | | 2 | 李四 | 102 | 102 | 市场部 | | 3 | 王五 | NULL| NULL| NULL | -- 员工无部门,右表字段为 NULL
RIGHT JOIN(保留所有部门):
SELECT * FROM employees RIGHT JOIN departments ON employees.department_id = departments.department_id;
结果:
| 1 | 张三 | 101 | 101 | 技术部 | | 2 | 李四 | 102 | 102 | 市场部 | | NULL| NULL| jsNULL| 103 | 财务部 | -- 部门无员工,左表字段为 NULL
总结
- INNER JOIN:精确匹配,适合需要严编程格关联的场景。
- LEFT JOIN:保留左表全部数据,适合主从表查询(如“所有员工及其部门”)。
- RIGHT JOIN:保留右表全部数据,使用较少(通常用
LEFT JOIN
调换表顺序替代)。
以上就是MySQL深度分页问题的三种解决方法的详细内容,更多关于MySQL深度分页问题的资料请关注编程客栈(www.devze.com)其它相关文章!
精彩评论