如何利用SQL嵌套查询在不使用LIMIT的情况下进行分页?

作者:袖梨 2026-06-30
不使用 LIMIT 也能分页,需借助行号变量、TOP 嵌套或窗口函数;MySQL 变量法需同语句初始化且依赖 ORDER BY,高并发易错;SQL Server/Access 用 TOP 子查询依赖排序字段索引;通用方案是窗口函数 ROW_NUMBER() 嵌套过滤,业务条件须置于内层。

不使用 LIMIT 也能分页,但必须换思路:靠行号变量、TOP 嵌套或窗口函数兜底。直接删掉 LIMIT 还想用原逻辑,大概率查出错乱或重复数据。

MySQL 用变量模拟行号分页(仅限低并发小数据)

本质是手动编号再过滤,@rownum 在子查询里累加,外层用 WHERE 截取范围。它不依赖 LIMIT,但有严重限制:

  • 必须显式 ORDER BY,否则行号顺序不可控
  • @rownum 初始化必须在同一条语句内完成,分开执行(如先 SETSELECT)会失效
  • 高并发下变量可能被多个连接覆盖,结果错乱
  • MySQL 8.0+ 推荐改用 ROW_NUMBER() 窗口函数,更可靠

示例:

SELECT * FROM (<br>  SELECT @rownum := @rownum + 1 AS rn, t.*<br>  FROM orders t,<br>       (SELECT @rownum := 0) r<br>  ORDER BY created_at DESC<br>) tmp<br>WHERE rn BETWEEN 21 AND 30;

SQL Server / Access 用 TOP + 子查询实现分页

这是 LIMIT 缺失时最主流的替代方案,核心是“先取前 N 条 ID,再反向筛选”。Access 必须这么写,SQL Server 在 2012 前也常用:

  • 第一页:直接 SELECT TOP 10 *ORDER BY
  • 后续页:用子查询找出上一页最大/最小排序字段值(比如 id),再 WHERE id > xxx 范围扫描
  • 必须确保排序字段有索引,否则子查询 ORDER BY ... TOP N 会全表扫
  • Access 不支持 OFFSET,且 TOP 不能和 ORDER BY 分开写——SELECT TOP 10 * FROM t ORDER BY id 才有效,SELECT TOP 10 * FROM (SELECT * FROM t ORDER BY id) 会报错

示例(SQL Server 第二页,每页 10 条):

SELECT TOP 10 *<br>FROM orders<br>WHERE id > (<br>  SELECT MAX(id)<br>  FROM (<br>    SELECT TOP 10 id<br>    FROM orders<br>    ORDER BY id<br>  ) t<br>)<br>ORDER BY id;

所有数据库通用:窗口函数 + 拆层绕过 LIMIT 限制

MySQL 8.0+、PostgreSQL、SQL Server 都支持 ROW_NUMBER(),但注意:不能和 LIMIT 同级共存。必须把带窗口的查询包进子查询,再在外层截取:

  • 错误写法:SELECT *, ROW_NUMBER() OVER (ORDER BY id) rn FROM t LIMIT 10 → MySQL 直接报错 “Window function is not allowed in this context”
  • 正确写法:窗口计算放内层,LIMIT 放外层;如果真要彻底不用 LIMIT,就用 WHERE rn BETWEEN x AND y
  • 业务条件(如 WHERE status = 'shipped')必须写在窗口查询的最内层,否则行号基于全表生成,分页就偏了

示例(纯窗口分页,无 LIMIT):

SELECT * FROM (<br>  SELECT *, ROW_NUMBER() OVER (ORDER BY created_at DESC) AS rn<br>  FROM orders<br>  WHERE status = 'shipped'<br>) t<br>WHERE t.rn BETWEEN 21 AND 30;

真正难的不是写出语法正确的嵌套,而是保证排序字段稳定、索引生效、内外过滤条件完全一致。少一个 WHERE,多一层无意义包装,分页就可能漏数据或重复。

相关文章

精彩推荐