直接用标准OFFSET FETCH最省事,但需确认数据库版本:SQL Server 2012+、PostgreSQL 8.4+、Oracle 12c+原生支持,MySQL 8.0.22+仅实验性支持且不推荐;版本不满足则报语法错误,跨库应优先依赖ORM方言自动适配而非手写多套SQL。

直接用标准 OFFSET FETCH 最省事,但得确认数据库版本
SQL Server 2012+、PostgreSQL 8.4+、Oracle 12c+ 都原生支持 OFFSET 和 FETCH,写法统一、语义清晰,是跨库兼容性最好的起点。只要你的目标环境满足最低版本要求,就别绕弯子用 ROW_NUMBER() 或 ROWNUM 嵌套。
常见错误现象:在 SQL Server 2008 上执行 OFFSET 10 ROWS FETCH NEXT 20 ROWS ONLY,报错 Incorrect syntax near 'OFFSET' —— 这不是语法错,是版本不支持。
- MySQL 8.0.22+ 才开始实验性支持
OFFSET ... FETCH,但默认关闭,且不推荐用于生产;仍应坚持用LIMIT - Oracle 11g 及更早版本完全不识别
OFFSET,强行使用会触发ORA-00933: SQL command not properly ended - PostgreSQL 虽支持,但若排序字段含
NULL,不同版本对NULLS FIRST/LAST的默认行为可能不一致,导致分页结果跳行
用 ORM 框架自动适配,别手写多套 SQL
MyBatis-Plus 的 Page 对象、PageHelper 插件、Hibernate 的 setFirstResult/setMaxResults,底层都依赖方言(dialect)识别数据库类型,自动生成对应分页语法。你只写一个逻辑,框架负责翻译成 LIMIT 10 OFFSET 20、OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY 或 ROWNUM 嵌套。
容易踩的坑:在 MyBatis XML 中手写 <select> 时,如果用了 ${page.offset} 拼接 SQL,就彻底绕过了方言机制,变成硬编码,一换库就崩。
- MyBatis-Plus 的
QueryWrapper+Page是安全的,它走的是拦截器重写 SQL 流程 - Spring Data JPA 的
Pageable同样可靠,但注意@Query注解里的原生 SQL 不受自动分页影响 - 自己封装的 JDBC 工具类若用字符串拼接
" LIMIT " + offset + " , " + size,本质就是 MySQL 专用,切 Oracle 立刻报错
手写兼容 SQL 的底线:避免嵌套子查询和数据库特有函数
如果必须写原生 SQL(比如报表引擎、DBA 脚本),要守住两条线:不用 TOP、不用 ROWNUM、不用 ISNULL;排序字段必须有索引,且不能对它用函数(如 ORDER BY UPPER(name))。
典型翻车场景:在金仓(KingbaseES)里写 SELECT * FROM t ORDER BY id OFFSET ? ROWS FETCH NEXT ? ROWS ONLY 是 OK 的,但加一句 WHERE create_time > DATEADD(day, -7, GETDATE()) 就挂了——GETDATE() 是 SQL Server 函数,金仓认不出来。
- 时间函数统一用 ANSI 标准:
CURRENT_DATE、INTERVAL '7 days',而不是SYSDATE或NOW() - 空值处理用
COALESCE(col, 'default'),别用ISNULL或NVL - 如果数据库连
OFFSET FETCH都不支持(比如旧版 Oracle),唯一可退守的通用写法是两层子查询 +ROWNUM或ROW_NUMBER(),但必须确保外层过滤条件写在最外层,否则 Oracle 会先截断再排序
深分页性能比语法兼容更致命
语法能跑通不代表能用。当 OFFSET 超过 10 万时,MySQL、PostgreSQL、SQL Server 全部会变慢,因为优化器仍需扫描并丢弃前面所有行。这时语法再“兼容”,也救不了响应时间。
真正该优先考虑的不是“怎么写才不出错”,而是“怎么查才不拖垮数据库”。游标分页(cursor-based pagination)用上一页最后一条记录的主键或时间戳做条件,例如 WHERE id > 123456 LIMIT 20,性能稳定,且天然规避了跨库语法差异。
- 游标分页无法跳转任意页码,但适合无限滚动、消息流等主流场景
- 复合排序字段(如
ORDER BY status, created_at, id)必须全部出现在WHERE条件中,否则可能漏数据或重复 - 时间字段做游标时,务必确认其精度和时区一致性;MySQL 的
DATETIME和 PostgreSQL 的TIMESTAMPTZ行为不同,直接比较可能出错
实际项目里,语法兼容只是第一关;排序字段有没有索引、偏移量是否过大、数据变更是否频繁,这些细节才是真正卡住上线的点。

















