老版本mysql(如5.5、5.6)稳定性高、兼容性好,但已停止支持,缺乏json、窗口函数等新特性,且性能与安全性明显落后于8.0/8.4等现代版本。

老版本 MySQL(
直接用 LIMIT 或 OFFSET FETCH 会报错,比如 MySQL 报 This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery',SQL Server 报缺少关键字。核心思路是:不用“跳过 N 行”,而是“给每行编号,再按编号筛选”。
-
ROW_NUMBER()是首选——但注意:MySQL - 若连
ROW_NUMBER()都没有(如 MySQL 4.x),只能靠自连接或变量模拟行号,但必须配合ORDER BY字段唯一且有索引,否则序号不稳定 - 示例(SQL Server 2005)取第 21–30 行:
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM users ) t WHERE t.rn BETWEEN 21 AND 30
Oracle 11g 及更早版本的 ROWNUM 为什么不能直接写 WHERE ROWNUM BETWEEN 21 AND 30?
ROWNUM 是在结果生成时即时赋值的,它从 1 开始递增,且只对已进入结果集的行生效。所以 WHERE ROWNUM >= 21 永远不会命中——因为第 1 行进来时 ROWNUM=1,不满足条件就被过滤掉,后续行根本没机会生成 ROWNUM=21。
- 必须嵌套两层:外层筛
ROWNUM ,内层先确保至少有 30 行进来,再在外层限制下限 - 正确写法:
SELECT * FROM ( SELECT a.*, ROWNUM rn FROM ( SELECT * FROM users ORDER BY id ) a WHERE ROWNUM = 21 - 漏掉内层
ORDER BY会导致分页结果乱序,且ROWNUM分配顺序不可控
没有窗口函数、又不想嵌套太深时,用自增 ID 做范围查询是否可行?
可行,但前提是目标字段(如 id)严格递增、无删除空洞、且业务允许按 ID 而非逻辑顺序分页。这不是通用截取,而是“用主键代替偏移量”的降级方案。
- 例如取第 21–30 条记录(假设 ID 连续):
SELECT * FROM users WHERE id BETWEEN 21 AND 30 ORDER BY id
- 问题在于:ID 不连续(删过数据)、ID 非自增(UUID)、或排序依赖时间戳而非 ID 时,结果会跳行或重复
- 真正安全的做法是先查出第 20 条的 ID:
SELECT id FROM users ORDER BY id LIMIT 20, 1(如果支持),再用该 ID 做WHERE id > ?查询——但这就又回到LIMIT依赖上了
为什么用子查询 + NOT IN 实现分页在大数据量下反而更慢?
比如 SQL Server 中:SELECT TOP 10 * FROM users WHERE id NOT IN (SELECT TOP 20 id FROM users ORDER BY id)。表面看避开了 OFFSET,实际执行时数据库仍需扫描全表生成子查询结果,且 NOT IN 对 NULL 敏感、无法走索引。
- 子查询返回 20 行,主查询要逐行比对
id是否不在这个集合里,复杂度接近 O(n×m) - 若
id字段无索引,或NOT IN子句中含NULL,整条语句可能返回空结果 - 更稳妥的替代是
LEFT JOIN + IS NULL,但依然不如ROW_NUMBER()或游标分页直观
真正难的不是写出能跑的 SQL,而是让同一套逻辑在不同旧版本数据库里都返回确定、可复现的结果——行号生成时机、排序稳定性、NULL 处理、索引覆盖,任何一个环节松动,分页就变成抽签。











