游标分页并非万能,其限制在于仅支持顺序翻页、依赖唯一有序索引且无法直接跳转任意页;替代方案包括子查询定位id、redis缓存总数或information_schema估算行数。

当用户点击第500页或导出接口触发百万级偏移查询时,MySQL会扫描并丢弃前50万行再返回结果,PHP进程常因超时中断,页面白屏或报504错误,这不是代码写得不够漂亮,而是分页逻辑本身已失效。
停用OFFSET,改用游标分页
第一步:确保排序字段有唯一、非空、带索引的列,如自增id或created_at时间戳。没有索引的游标分页比OFFSET还慢,因为WHERE条件无法走索引。
第二步:首次请求不传游标,查出第1页数据+1条冗余记录,例如每页50条就查51条:SELECT * FROM posts ORDER BY id DESC LIMIT 51。
第三步:取第51条记录的id值(比如id=987654),作为下一页的游标参数,客户端下次请求带上cursor=987654。
第四步:下一页SQL改为SELECT * FROM posts WHERE id ——注意是<code>而非<code>,否则可能重复返回同一条记录。
这一步必须用,<strong>【=会导致同一条记录在相邻两页重复出现】</strong>。
总记录数不再硬查COUNT(*)
方法一:只判断“是否有下一页”,查51条后看是否真拿到51条。如果只拿到50条,说明本页就是最后一页;拿到51条则保留第51条id用于下一页,但只渲染前50条。
方法二:用Redis缓存总数,每次新增/删除帖子时执行INCRBY post_count 1或DECRBY post_count 1,读取时直接GET post_count。缓存失效策略设为30分钟自动过期,避免计数长期漂移。
方法三:对精确总数有强依赖的后台管理页,改用SELECT table_rows FROM information_schema.tables WHERE table_name = 'posts',InnoDB下该值为估算值,误差在±10%内,但查询毫秒级响应。
PHP层预处理与安全加固
接收page或cursor参数时,必须强制类型转换:$cursor = (int)$_GET['cursor'] ?: 0;。若传入cursor=abc,(int)会转成0,后续WHERE id
拼接SQL前校验cursor是否为正整数:if ($cursor ,防止恶意构造负数或超大数值击穿数据库。
使用PDO预处理语句,绑定参数类型:$stmt->bindValue(':cursor', $cursor, PDO::PARAM_INT);,这一步操作起来很简单,直接把变量丢进bindValue就行。
【不要用字符串拼接SQL,哪怕只拼一个数字】,$sql = "WHERE id cursor=123 OR 1=1绕过限制。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











