不使用 LIMIT 也能分页,需借助行号变量、TOP 嵌套或窗口函数;MySQL 变量法需同语句初始化且依赖 ORDER BY,高并发易错;SQL Server/Access 用 TOP 子查询依赖排序字段索引;通用方案是窗口函数 ROW_NUMBER() 嵌套过滤,业务条件须置于内层。
不使用 LIMIT 也能分页,但必须换思路:靠行号变量、TOP 嵌套或窗口函数兜底。直接删掉 LIMIT 还想用原逻辑,大概率查出错乱或重复数据。
本质是手动编号再过滤,@rownum 在子查询里累加,外层用 WHERE 截取范围。它不依赖 LIMIT,但有严重限制:
ORDER BY,否则行号顺序不可控@rownum 初始化必须在同一条语句内完成,分开执行(如先 SET 再 SELECT)会失效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;
这是 LIMIT 缺失时最主流的替代方案,核心是“先取前 N 条 ID,再反向筛选”。Access 必须这么写,SQL Server 在 2012 前也常用:
SELECT TOP 10 * 加 ORDER BY
id),再 WHERE id > xxx 范围扫描ORDER BY ... TOP N 会全表扫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;
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,多一层无意义包装,分页就可能漏数据或重复。