在处理海量数据时,一次性将所有记录从数据库中查询并展示出来,不仅会极大地消耗网络带宽和服务器资源,也会导致前端页面渲染缓慢,用户体验极差,分页查询技术应运而生,它允许我们每次只从数据库中获取一小部分数据(即一页),从而有效解决了上述问题,其核心思想是,告诉数据库从哪里开始取数据,以及取多少条数据。

分页的基本原理
分页查询通常依赖于两个关键参数:
- 每页条数:指每一页需要展示的记录数量,这是一个固定值,例如10、20或50。
- 页码:用户当前查看的页数,从1开始。
在数据库层面,这两个参数通常被转换为更具操作性的两个概念:
- 偏移量:指在返回结果之前,需要跳过的记录数量,计算公式为:
OFFSET = (页码 - 1) * 每页条数,获取第2页,每页10条,则偏移量为(2 - 1) * 10 = 10,意味着跳过前10条记录。 - 限制条数:指偏移之后,需要获取的记录数量,即
每页条数。
主流数据库的分页实现语法
尽管分页的原理相通,但不同的数据库管理系统(DBMS)提供了不同的SQL语法来实现它,以下是一些主流数据库的实现方式。
MySQL / PostgreSQL / SQLite
这些数据库使用最为直观和广泛的 LIMIT ... OFFSET ... 语法,结构清晰,易于理解。
语法结构:
SELECT column1, column2, ... FROM table_name ORDER BY some_column LIMIT [限制条数] OFFSET [偏移量];
示例:
假设有一个 users 表,我们想按 id 升序排列,获取第3页的数据,每页10条。
- 页码:3
- 每页条数:10
- 偏移量:(3 – 1) * 10 = 20
SELECT id, username, email FROM users ORDER BY id LIMIT 10 OFFSET 20;
这条语句会先跳过前20条记录,然后获取接下来的10条记录。
SQL Server (2012及以上版本)
SQL Server 推荐使用 OFFSET ... FETCH ... 子句,这与标准SQL更为接近。
语法结构:

SELECT column1, column2, ... FROM table_name ORDER BY some_column OFFSET [偏移量] ROWS FETCH NEXT [限制条数] ROWS ONLY;
示例:
同样获取第3页,每页10条的数据:
SELECT id, username, email FROM users ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
对于更早版本的SQL Server,通常需要使用 ROW_NUMBER() 窗口函数结合子查询或公用表表达式(CTE)来实现,相对复杂一些。
Oracle (12c及以上版本)
Oracle 12c也引入了与SQL Server类似的 OFFSET ... FETCH ... 语法,大大简化了分页操作。
语法结构:
SELECT column1, column2, ... FROM table_name ORDER BY some_column OFFSET [偏移量] ROWS FETCH NEXT [限制条数] ROWS ONLY;
示例:
SELECT id, username, email FROM users ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
在Oracle 12c之前,分页主要依赖于 ROWNUM 伪列,使用 ROWNUM 时需要注意,它是在数据获取后、排序前分配的,因此通常需要三层嵌套子查询来确保分页的准确性,这曾让许多开发者感到困扰。
性能考量与深度分页问题
标准的 LIMIT/OFFSET 分页方式在偏移量不大的情况下表现良好,当需要查询非常靠后的页面时(在百万级数据表中查询第99999页),就会出现所谓的“深度分页”问题。
问题根源: 数据库在执行 LIMIT 1000000, 10 这样的查询时,并不能直接跳到第1000000条记录,它需要先扫描并丢弃前面的1000000条记录,然后才返回接下来的10条,这个过程随着偏移量的增大,性能会急剧下降。
解决方案:键集分页

为了解决深度分页的性能瓶颈,可以采用键集分页,也称为游标分页,它不使用偏移量,而是记住上一页最后一条记录的唯一标识(通常是自增ID或具有唯一索引的列)。
实现思路:
查询下一页时,使用 WHERE 子句来直接定位到上一页最后一条记录之后的数据。
示例:
假设第1页最后一条记录的 id 是 98。
-- 第一页查询 SELECT id, username, email FROM users ORDER BY id LIMIT 10; -- 假设最后一条id是98,查询下一页 SELECT id, username, email FROM users WHERE id > 98 -- 直接定位到上一页最后一条记录之后 ORDER BY id LIMIT 10;
这种方式的性能非常稳定,因为它可以利用索引快速定位到起始点,无需扫描和丢弃大量数据,缺点是无法实现“跳转到第N页”的功能,只适合“上一页/下一页”的导航模式。
语法速查表
| 数据库 | 推荐语法 | 示例 (获取第3页, 每页10条) |
|---|---|---|
| MySQL / PostgreSQL / SQLite | LIMIT ... OFFSET ... | ... LIMIT 10 OFFSET 20; |
| SQL Server (2012+) | OFFSET ... FETCH ... | ... OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY; |
| Oracle (12c+) | OFFSET ... FETCH ... | ... OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY; |
相关问答FAQs
问题1:为什么分页查询强烈建议使用 ORDER BY 子句?
解答: 如果不使用 ORDER BY,数据库返回的记录顺序是不确定的,这意味着在两次相同的分页查询之间,数据库可能会以不同的顺序返回数据,这会导致两个严重问题:1)数据重复:同一页的某些记录可能在下一页再次出现,2)数据丢失:某些记录可能被完全跳过。ORDER BY 确保了每次查询都有一个稳定、可预测的排序,使得 OFFSET 能够准确地跳过指定数量的行,从而保证分页的正确性和一致性。
问题2:除了键集分页,还有其他方法可以优化深度分页吗?
解答: 键集分页是解决深度分页最主流和高效的方案,除此之外,还有一些辅助或替代的思路,但它们各有取舍,可以采用“延迟关联”或“子查询优化”的方式,基本思想是,先在覆盖索引(包含排序字段和主键的索引)上快速定位出所需页面的主键ID,然后再通过这些ID去关联查询完整的行数据,这种方法减少了回表操作的数据量,比原始的 LIMIT/OFFSET 要快,但性能依然会随着偏移量增加而下降,只是下降曲线更平缓,相比之下,键集分页的性能则与页码无关,始终保持高效,在性能要求极高的场景下,键集分页仍是首选。
【版权声明】:本站所有内容均来自网络,若无意侵犯到您的权利,请及时与我们联系将尽快删除相关内容!
发表回复