什么是MySQL的深度分页?
MySQL的深度分页是指在数据量较大的情况下,从数据库中获取位于较高页码的数据。例如,当数据表中有大量记录时,要获取接近末尾的几页数据,这种情况就称为深度分页。
深度分页的性能问题
当使用常规的分页查询(
LIMIT 和 OFFSET)进行深度分页时,MySQL会出现性能问题。这是因为MySQL在执行分页查询时,会先扫描并跳过 OFFSET 指定数量的记录,然后再返回 LIMIT 指定数量的记录。举个例子,假设有一个包含一百万条记录的表,查询
LIMIT 900000, 1000 的执行过程如下:示例表字段如下,添加了name字段索引



MySQL执行上述查询时,会先扫描前900000条数据,然后再返回第900001到901000条数据。作为对比,可以看下直接查询前1000条数据的执行时间。

这种方式在数据量大时会导致以下问题:
- 大量的磁盘I/O操作:随着
OFFSET值的增大,MySQL需要扫描并跳过越来越多的记录,这会导致大量的磁盘读取操作。
- 较慢的查询响应时间:因为MySQL需要处理大量不必要的数据,这会导致查询时间显著增加。
- 内存消耗增加:处理大数据集时,MySQL可能需要更多的内存来管理临时数据。
深度分页的解决方案
为了优化深度分页的性能,可以使用一些替代方法来避免直接使用
OFFSET,以下是一些常见的解决方案及其示例代码:1. 基于索引的分页
通过索引字段(如主键)来进行分页查询,这种方法避免使用
OFFSET,只需记住上一次查询的最后一个索引值。在这个例子中,子查询先找到第900000行的
id 值,然后通过该 id 值进行后续的分页查询。
2. 延迟关联(Deferred Join)
先通过索引字段查询出所需的主键范围,然后再进行关联查询。这种方法适用于有大量列的表,可以减少不必要的列扫描。
通过子查询仅获取所需的主键,再通过主键进行关联查询,提升性能。

下面这个sql例子更能说明延迟关联的性能优化

3. 覆盖索引(Covering Indexes)
如果查询涉及的字段都在索引中,可以使用覆盖索引来避免访问表数据。
使用覆盖索引,查询只从索引中获取数据,减少I/O操作。
4. 游标分页
通过自定义的游标来跟踪分页状态,避免重复计算
OFFSET。每次查询后更新
@offset 的值,记录当前页最后一行的 id,下一次查询时从该 id 开始。
通过优化分页查询,可以显著提升深度分页的性能,避免不必要的磁盘I/O操作和查询延迟。在实际应用中,根据数据结构和查询需求选择合适的优化方法,有助于提升系统的整体性能。
优化sql的过程中,可以使用
EXPLAIN 来帮助分析和优化SQL查询使用 EXPLAIN
EXPLAIN提供了有关查询计划的信息,包括如何访问表、使用的索引、估计的行数等。要使用
EXPLAIN,只需在查询前加上EXPLAIN关键字。例如:结果解释
执行
EXPLAIN后,会返回一张表,包含以下列:- id: 查询中每个选择查询的标识符。
- select_type: 查询的类型,例如
SIMPLE(简单查询)、PRIMARY(主查询)、SUBQUERY(子查询)等。
- table: 查询的表。
- partitions: 匹配的分区。
- type: 联接类型,表示表的访问方式,例如
ALL(全表扫描)、index(索引扫描)、range(范围扫描)、ref(非唯一索引扫描)、eq_ref(唯一索引扫描)等。
- possible_keys: 可能用于查询的索引。
- key: 实际使用的索引。
- key_len: 使用的索引的长度。
- ref: 哪个列或常量与索引一起使用。
- rows: MySQL 估计必须要读取的行数。
- filtered: 经过过滤条件后返回的行百分比。
- Extra: 附加信息,例如
Using where(使用了WHERE子句)、Using index(使用了覆盖索引)等。
对于type,联接类型,不同的访问方式对查询性能有显著影响。
- ALL:性能最差,应尽量避免。
- index:优于全表扫描,但仍需优化。
- range:性能较好,适用于范围查询。
- ref:性能很好,适用于非唯一索引查询。
- eq_ref:性能最佳,适用于唯一索引或主键查询。
示例
假设有一个名为
users的表,结构如下:插入一些数据:
执行查询并使用
EXPLAIN分析:返回结果可能如下:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
1 | SIMPLE | users | NULL | ref | idx_username | idx_username | 767 | const | 1 | 100.00 | Using where |
解释结果
- id: 1,表示这是查询中的第一个且唯一的查询。
- select_type: SIMPLE,表示这是一个简单查询。
- table: users,表示查询的表是
users。
- partitions: NULL,表示未使用分区。
- type: ref,表示使用了非唯一索引扫描。
- possible_keys: idx_username,表示可能使用的索引是
idx_username。
- key: idx_username,表示实际使用的索引是
idx_username。
- key_len: 767,表示索引的长度。
- ref: const,表示使用了常量进行匹配。
- rows: 1,表示MySQL估计需要读取1行记录。
- filtered: 100.00,表示经过过滤条件后返回的行百分比是100%。
- Extra: Using where,表示使用了WHERE子句进行过滤。
通过分析
EXPLAIN的结果,可以了解查询的执行计划,并针对可能的性能问题进行优化,例如添加或修改索引、重写查询等。- 作者:Yibin
- 链接:https://yibin.dev/article/c616b3d7-c40f-461d-90f6-01601f1d52d4
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章






