高并发下limit offset,size翻页必然不准,必须用游标分页:基于主键或时间戳的where条件替代offset,要求排序字段有索引、非空、唯一,并严格校验cursor和size。

高并发下用 LIMIT offset, size 翻页必然不准——不是 PHP 函数写得不对,是 SQL 语义和数据库并发行为共同决定的。
为什么 OFFSET 在高并发下会漏数据或重复
MySQL 不保证未加 ORDER BY 的查询顺序;即使加了,若排序字段不唯一(比如 ORDER BY status),同一批记录可能被数据库重排。更关键的是:LIMIT 10000, 20 要先扫描并丢弃前 10000 行——这期间若有记录被删/插入,第 501 页就可能跳过某条或重复出现同一条。
- 用户 A 查第 1 页(
LIMIT 0, 20),拿到最后一条id = 1024 - 用户 B 在此时插入一条
id = 1020的记录 - 用户 A 查第 2 页(
LIMIT 20, 20),结果里可能不含id = 1020,也可能在第 3 页又看到它
必须用游标分页:基于主键或时间戳的 WHERE 条件
替代 OFFSET 的唯一可靠方式是游标分页,核心是「用上一页末尾值作为下一页起点」。
- 要求排序字段必须有索引、非空、唯一(如自增
id或带纳秒精度的created_at) - 第一页查:
SELECT * FROM posts ORDER BY id ASC LIMIT 20 - 拿到最后一条的
id = 12345,下一页查:SELECT * FROM posts WHERE id > 12345 ORDER BY id ASC LIMIT 20 - 前端 URL 里传的是
?cursor=12345,而非?page=2;服务端要校验该cursor是否真实存在且未被篡改
游标分页的两个易错点
很多人以为换了 WHERE id > ? 就万事大吉,但实际部署时容易栽在这两处:
-
WHERE id > ?只支持单向翻页(下一页)。要支持「上一页」,得查WHERE id ,然后在 PHP 层把结果数组 <code>array_reverse(),否则顺序反了 - 如果业务强制要求按非唯一字段排序(比如热度),不能实时算
ORDER BY hot_score,必须固化值:加一个hot_score_cached字段,用定时任务或写操作时更新,再基于它建索引
参数校验和边界控制不能省
哪怕用了游标分页,cursor 和 size 仍需严格过滤,否则可能被绕过或拖垮数据库:
- 用
filter_var($_GET['cursor'], FILTER_VALIDATE_INT)校验游标值,非法值直接返回 400 -
size必须限制范围(如min(max((int)$_GET['size'], 1), 100)),禁止传size=999999 - 游标值需二次校验:查一次
SELECT id FROM table WHERE id = ?,确认该记录确实存在且未被逻辑删除 - 不提供「跳转指定页码」入口;真需要,前端应转成游标(比如第 100 页 → 先查第 1 页,再逐页 fetch,或用覆盖索引快速定位)
游标分页不是“换种写法”,而是彻底放弃页码思维——它的准确性和性能都依赖于排序字段的稳定性与索引有效性,任何试图在上面叠加工具函数或自动 fallback 到 OFFSET 的设计,都会在高并发下暴露一致性裂缝。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











