MySQL和PostgreSQL的LIMIT/OFFSET均需全扫描并逐行计数,OFFSET越大性能越差;游标分页通过WHERE+ORDER BY+LIMIT实现索引范围查询,避免计数开销,但要求排序字段组合全局唯一。

MySQL里LIMIT和OFFSET是怎么查数据的
它不是跳过前N行再取M行,而是从头扫描、逐行计数——哪怕你OFFSET 1000000,MySQL也得先读完前100万行,再开始返回结果。这就是为什么深分页越往后越慢。
常见错误现象:SELECT * FROM orders ORDER BY id LIMIT 10 OFFSET 1000000执行要好几秒,但OFFSET 0只要几毫秒。
- 真正执行时,MySQL会按
ORDER BY字段排序后,用游标式遍历:每行都算一次偏移量,直到计数器达到OFFSET值才开始收集LIMIT指定的行 - 如果没加
ORDER BY,行为不可靠——不同版本、不同存储引擎可能返回顺序不一致,OFFSET失去意义 -
OFFSET越大,I/O和CPU开销线性增长;索引能加速排序,但无法跳过计数过程
PostgreSQL的LIMIT/OFFSET有没有优化
和MySQL一样,PostgreSQL也是全扫描+计数,没有跳过机制。但它的执行计划更透明,可以用EXPLAIN清楚看到“Seq Scan”或“Index Scan”后面跟着“Limit”节点,且明确标出rows removed by limit——这个数字就是被跳过的行数。
使用场景:适合后台管理类系统做简单翻页,但千万级表+频繁深分页必须换方案。
- 即使有覆盖索引,
OFFSET仍需定位到第N+1行位置,不能直接seek - 并发更新下,
OFFSET分页会出现漏数据或重复(比如中间删了10行,第二页就少10条) - PostgreSQL 12+支持
FETCH FIRST n ROWS ONLY,语义等价于LIMIT,但底层无性能差异
为什么游标分页(Cursor-based Pagination)能绕过OFFSET瓶颈
因为它不依赖“第几页”,而是记住“上一页最后一条的排序键值”,下一次查询直接WHERE sort_key > last_value ORDER BY sort_key LIMIT N——数据库可以走索引range scan,完全避开计数逻辑。
典型错误:把主键ID当游标却忽略重复值或非单调场景(如按created_at分页,同一时间戳有多条)。
- 必须确保
ORDER BY字段组合在全表唯一,否则可能漏/重;常用做法是追加主键:ORDER BY created_at, id - 第一次请求不能用
OFFSET,得用WHERE created_at > 'xxx' ORDER BY created_at, id LIMIT 20 - 客户端必须保存上一页末尾的
created_at和id两个值,缺一不可
SQL Server和Oracle的等效写法为什么看起来不一样
SQL Server用OFFSET-FETCH(如ORDER BY id OFFSET 100 ROWS FETCH NEXT 10 ROWS ONLY),Oracle 12c+用OFFSET ... ROWS FETCH NEXT ... ROWS ONLY,语法更接近标准,但底层仍是逐行计数——和MySQL/PG一样慢。
容易踩的坑:OFFSET子句必须紧跟ORDER BY,且ORDER BY不能为空;SQL Server早期版本(2012前)只能靠ROW_NUMBER()模拟,性能更差。
-
ROW_NUMBER() OVER (ORDER BY id)+ 子查询虽然能实现分页,但会强制物化全部结果集,内存和临时表压力更大 - Oracle的
ROWNUM写法(WHERE ROWNUM )不支持跳过,必须嵌套才能模拟<code>OFFSET,且极易写错范围条件 - 所有数据库中,带
OFFSET的查询都无法被查询缓存有效复用(因参数变化太频繁)
真正的难点不在语法,而在于业务是否允许放弃“第N页”这个概念——游标分页没法跳转到任意页码,这是用性能换一致性的必然代价。









