oracle分页不能直接用offset/limit,因11g及更早版本不支持该语法,dapper也不内置分页适配,需手写sql;12c+虽支持offset...fetch,但易因参数绑定错误或版本不匹配引发ora-01036等异常,且生产环境排查困难。

Oracle分页为什么不能直接用OFFSET/LIMIT
因为Oracle 11g及更早版本不支持OFFSET/LIMIT语法,.NET 6里Dapper本身也不内置分页适配逻辑——它只是轻量级ORM,所有SQL都得你手写。即使连Oracle 12c+的OFFSET ... FETCH NEXT,在Dapper中也得自己拼,且容易因参数绑定顺序或类型不匹配导致ORA-01036: illegal variable name/number错误。
常见踩坑点:
• 把OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY直接套用到11g数据库上,报ORA-00933: SQL command not properly ended
• 在WHERE子句后直接接OFFSET,没套ROWNUM或ROW_NUMBER()伪列
• @skip传入负数或非整数,Oracle驱动(如Oracle.ManagedDataAccess)会静默转成0但结果错乱
Dapper调用ROW_NUMBER()分页的正确写法
这是兼容Oracle 11g–19c最稳的方式:外层查ROW_NUMBER()序号,内层放原始查询(含ORDER BY),再用WHERE ROWNUM BETWEEN或二次过滤。关键不是“怎么写SQL”,而是“怎么让Dapper安全传参并映射”。
- 必须把排序字段显式写进内层
SELECT,否则ROW_NUMBER() OVER (ORDER BY ...)无法确定顺序 - 外层查询别名要明确,比如
SELECT * FROM (SELECT ..., ROW_NUMBER() OVER (...) rn FROM ...) t WHERE t.rn BETWEEN @start AND @end,否则Dapper可能映射失败 -
@start和@end需是int类型,且@start = (page - 1) * pageSize + 1,@end = page * pageSize——注意不是从0开始 - 避免在
ORDER BY里用表达式(如UPPER(name)),否则ROW_NUMBER()可能不稳定,尤其数据量大时
示例SQL片段:
SELECT * FROM ( SELECT t.*, ROW_NUMBER() OVER (ORDER BY t.id DESC) rn FROM users t WHERE t.status = :status ) WHERE rn BETWEEN :start AND :end
Dapper调用时用connection.Query<user>(sql, new { status = 1, start = 1, end = 20 })</user>,注意参数名与SQL中:前一致(Oracle命名参数风格)。
如何让Dapper自动处理Oracle分页参数绑定
手动算start/end太易错,建议封装一个扩展方法,把page、pageSize转成底层需要的范围,并统一处理ORDER BY注入风险。
- 不要拼接
ORDER BY字段名——用白名单校验,比如只允许"id"、"created_time"等预设字段 - Oracle参数名区分大小写,
:Start和:start是两个参数,Dapper默认不自动转小写,所以SQL里全用小写冒号参数 - 如果查询带
OUT参数(比如要返回总记录数),得用OracleDynamicParameters,而不是基础DynamicParameters - 对
DateTime参数,Oracle.ManagedDataAccess默认用DATE类型,若字段是TIMESTAMP,需显式指定OracleDbType.TimeStamp,否则精度丢失
性能敏感点:COUNT(*)和分页SQL要不要拆开
要拆。Dapper执行一次分页查,通常还得另发一条COUNT(*)获取总数。别用SELECT COUNT(*) OVER()套在分页SQL里——看似省一次IO,实则让Oracle优化器放弃索引快速路径,全表扫一遍,尤其在千万级表上延迟翻倍。
- 分页主SQL走覆盖索引(
SELECT id, name FROM users WHERE status=1 ORDER BY id DESC),确保WHERE + ORDER BY字段有联合索引 -
COUNT(*)语句去掉ORDER BY和所有非WHERE字段,只留必要条件,避免执行计划被干扰 - 如果业务允许“估算总数”,可用
SELECT num_rows FROM all_tables WHERE table_name='USERS'查统计信息,比实时COUNT快两个数量级 - 缓存总数时注意事务隔离:可读已提交(READ COMMITTED)下,两次查询间插入/删除会导致总数不准,需评估业务容忍度
真实场景里,ROW_NUMBER()分页+独立COUNT是平衡兼容性、性能和准确性的底线方案。Oracle 12c+的OFFSET/FETCH虽简洁,但升级成本、驱动版本依赖和异常堆栈模糊(报错位置常指向绑定层而非SQL本身)让它在生产环境反而更难排查。











