易君召
易君召
发布于 2026-11-02 / 2 阅读
0

数据库分页方案性能对比

前置说明:下面对比以 MySQL 为主(InnoDB),同时补充 PostgreSQL、SQL Server 的差异;核心痛点不是 LIMIT,而是大偏移量 OFFSET N。 测试前提:主键自增、有合适索引、单表,无复杂 join,数据量 10w~1000w。

一、主流分页方式汇总

  1. LIMIT offset, size(传统偏移分页)

SELECT * FROM t ORDER BY id LIMIT 10000, 20;
  1. 主键书签分页(keyset / 游标分页,推荐)

SELECT * FROM t WHERE id > 10000 ORDER BY id LIMIT 20;
  1. 子查询先定位偏移(延迟关联)

SELECT a.* FROM t a
INNER JOIN (SELECT id FROM t ORDER BY id LIMIT 10000,20) b
ON a.id = b.id;
  1. ROW_NUMBER () 窗口函数分页

SELECT * FROM (
    SELECT *, ROW_NUMBER() OVER(ORDER BY id) AS rn FROM t
) tmp WHERE rn BETWEEN 10001 AND 10020;
  1. SQL Server OFFSET ... FETCH

SELECT * FROM t ORDER BY id OFFSET 10000 ROWS FETCH NEXT 20 ROWS ONLY;
  1. PostgreSQL LIMIT/OFFSET 和 MySQL 行为接近,另外支持 FETCH

二、性能对比表

分页方案

小偏移(第 1/2 页)

大偏移(第 1000 页 +)

索引依赖

缺点

适用场景

LIMIT offset,size

✅ 很快

❌ 极差

需要排序字段索引

offset 越大,数据库扫描丢弃的数据越多,扫描行数 = offset+size

只适合少量分页、后台管理,不允许跳页到很深

书签分页(keyset)

✅ 很快

✅ 稳定

排序字段必须建索引,最好是主键

不支持随机跳页(不能直接跳到第 1000 页),只能上一页 / 下一页

APP、信息流、滚动下拉,互联网首选

延迟关联(子查 id 再 join)

✅ 快

⚠️ 优于原生 LIMIT,但仍随 offset 变慢

排序字段索引

依然要扫描 offset 行,只是只扫索引、回表少

后台管理,必须支持跳页、不能改书签模式,折中方案

ROW_NUMBER()

✅ 尚可

❌ 很差

窗口函数需要全排序

MySQL8.0/PG/SQL Server 支持;底层要生成行号,大偏移代价高

多字段复杂排序、必须按行号跳页,尽量避免深分页

关键原理(MySQL InnoDB)

LIMIT 10000,20:

  1. 按索引有序扫描 10020 行

  2. 扔掉前面 10000 行,返回后面 20 行 👉 offset 越大,丢弃的数据越多,时间线性上涨。

书签分页 where id>xxx limit 20:

  1. 索引直接定位 id>xxx 的位置

  2. 只读取需要的 20 行 👉 无论第几页,扫描行数固定,性能稳定。

三、扩展:多字段排序下的书签分页

很多场景不是按 id,而是 order by create_time, id(防止时间重复顺序错乱)

-- 上一页最后一条:create_time='2026-10-01 12:00:00', id=12345
SELECT * FROM t 
WHERE create_time < '2026-10-01 12:00:00' 
   OR (create_time = '2026-10-01 12:00:00' AND id < 12345)
ORDER BY create_time DESC, id DESC
LIMIT 20;

联合索引:(create_time desc, id desc),否则性能暴跌。

限制:不能跳页;前端只能记住上一页最后一条的排序字段值,不能输入页码。

四、各个数据库特殊点

PostgreSQL

  • OFFSET 和 MySQL 一样,深分页慢;

  • 同样优先 keyset;

  • 支持 FETCH NEXT n ROWS ONLY,语法糖,底层和 LIMIT 无本质区别;

  • 大结果集排序会耗 shared buffer。

SQL Server

  • OFFSET ... FETCH 就是官方分页,本质也是偏移分页,深分页一样慢;

  • 窗口函数 ROW_NUMBER() 经常被业务用来分页,但千万不要深偏移;

  • 书签方案同样最优。

Oracle

  • 老版本:ROWNUM 嵌套分页,经典两层子查询,深分页开销巨大;

  • 12c+:OFFSET ... FETCH,同样偏移分页;

  • 最优依然是 keyset。

五、常见误区

  1. ❌ “加了索引 LIMIT 大偏移就很快” 索引只是让排序快,offset 丢弃行的开销依然存在。

  2. ❌ “ROW_NUMBER 比 LIMIT 性能更好” 小页码差别不大,深偏移下更慢,因为要计算、存储行号。

  3. ❌ “延迟关联可以无限深分页” 只是优化了回表,扫描 offset 行的成本还在,offset 几十万照样慢。

  4. ❌ 用 count (*) 算总条数 + 页码 count(*) 全表扫描,千万级表非常慢;信息流不要返回 total,后台管理可以缓存总数或者近似值(MySQL information_schema、PG 估算)。

六、选型建议

  1. C 端 APP、信息流、下拉滚动(无跳页需求) → 书签分页(keyset) ✅ 首选,性能最稳

  2. 后台管理系统,必须支持输入页码、跳转到任意页

    • 页码小:直接 LIMIT offset,size

    • 页码很大:不支持,或者限制最大页码;用延迟关联做优化

  3. 复杂报表、多表 join、多字段无序排序

    • 优先提前预聚合到数仓(ClickHouse / Doris),不要在 OLTP 库分页

  4. 千万级以上数据:尽量放弃 “随机页码分页” 的产品设计


本文原创作者:易君召,详见:https://www.yijunzhao.cn/authors/yijunzhao,转载请注明出处。

原文链接 https://www.yijunzhao.cn/archives/database-pagination-solutions-performance-comparison

欢迎访问 小易撩挨踢

https://www.yijunzhao.cn/