mysql大偏移量分页性能差,应改用where+order by+limit或游标分页;需校验page/size防注入与溢出;orm分页慎用count和内存分页。

MySQL LIMIT 用法和常见分页陷阱
直接在 SELECT 后加 LIMIT offset, size 是最常用做法,但大偏移量(比如 LIMIT 1000000, 20)会导致性能断崖式下降——MySQL 仍要扫描前 100 万行再丢弃。
- 用
WHERE id > ? ORDER BY id LIMIT 20替代LIMIT offset, 20,前提是主键或索引列单调递增且不跳变 - 避免
ORDER BY RAND()+LIMIT,全表随机排序开销极大,改用应用层抽样或预生成 ID 列表 -
LIMIT不会减少网络传输量:即使只取 20 行,MySQL 仍可能把整张表的字段元信息发给客户端,注意SELECT *的代价
应用端分页时如何安全传参和校验
前端传来的 page 和 size 必须严格过滤,否则容易触发 SQL 注入或内存溢出。
-
size建议硬限制在 1–100 之间,超出则返回400 Bad Request,防止用户故意传size=1000000 -
page转成offset = (page - 1) * size之前,先检查乘积是否超过预设阈值(如 500 万),超了就拒绝或自动降级为游标分页 - 不要信任前端传来的
offset值,后端必须重新计算,避免绕过校验
游标分页(Cursor-based Pagination)怎么落地
当数据实时变动频繁、传统 LIMIT 分页结果错乱(比如插入新记录导致某页重复或丢失),就得切到游标分页。
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
- 核心是用上一页最后一条记录的排序字段值(如
created_at或id)作为下一页起点:WHERE created_at > '2024-05-01 10:00:00' ORDER BY created_at LIMIT 20 - 必须确保排序字段有索引,且组合唯一(如
(created_at, id)),否则同时间戳多条记录会漏数据 - 游标值需 Base64 编码后传给前端,避免暴露原始字段内容和格式,也防止篡改(比如把时间改成负数)
ORM 框架里分页的隐藏开销
像 Django 的 QuerySet 或 Laravel 的 paginate() 看似方便,但默认会多执行一次 COUNT(*) 查询算总页数——对千万级表,这句 COUNT 可能比主查询还慢。
- 禁用总数统计:Django 用
object_list[paginator.start_index():paginator.end_index()]手动切片;Laravel 用simplePaginate() - MyBatis 中
RowBounds是内存分页,不是 SQL 层 LIMIT,大数据量时慎用 - Spring Data JPA 的
Pageable默认走count,若不需要总条数,改用Slice接口
游标分页没法跳转任意页,但胜在稳定和快;传统分页支持跳页,但 offset 越大越危险。选哪个,取决于你的业务是否允许“下一页/上一页”这种线性导航,还是必须支持“跳到第 87 页”。










