SQL 如何实现“游标分页”(keyset pagination)避免深分页慢
发布时间 - 2026-01-29 00:00:00 点击率:次keyset pagination 是一种基于排序字段值的游标分页方法,比 OFFSET 更快,因其跳过扫描直接定位;要求排序字段具唯一性,SQL 通过比较上一页末记录的 (created_at, id) 值实现高效分页。
什么是 keyset pagination,为什么比 OFFSET 快
OFFSET 分页在数据量大、页码靠后时会越来越慢,因为数据库必须扫描并跳过前面所有行。keyset pagination(又称游标分页)不依赖行号,而是用上一页最后一条记录的排序字段值作为下一页的起点,跳过扫描过程,直接定位。
它本质是“基于值的分页”,要求排序字段有唯一性或组合唯一性(比如 created_at, id),否则可能漏行或重复。
常见错误现象:OFFSET 100000 LIMIT 20 执行秒级甚至超时;而等价的 keyset 查询毫秒级返回。
如何写一个安全的 keyset 分页 SQL
核心是把“第 N 页”转换成“大于上一页最后一条的排序键”。假设按 created_at DESC 排序,且每页取 20 条:
- 第一页:直接查最大值,
SELECT * FROM posts ORDER BY created_at DESC, id DESC LIMIT 20 - 后续页:用上一页最后一条的
(created_at, id)做条件,注意方向要和 ORDER BY 一致 - 第二页示例(假设上一页最后一条是
('2025-05-01 10:00:00', 12345)):SELECT * FROM posts WHERE (created_at, id)
关键点:
- 多字段排序必须用行值比较(PostgreSQL/MySQL 8.0+ 支持),不能拆成两个独立条件(易出错)
- 方向必须严格匹配:DESC 对应
- 如果排序字段允许 NULL,需额外处理(例如加
IS NOT NULL)
MySQL 和 PostgreSQL 的语法差异与陷阱
MySQL 5.7 不支持行值比较,得改写为复合条件:
等价于上面的 MySQL 5.7 写法:WHERE created_at
PostgreSQL 支持标准行值语法,但要注意:
(a, b) 等价于a ,语义一致- 如果字段类型是
TIMESTAMP WITH TIME ZONE,时区不一致会导致游标失效
常见坑:
- 忘记给排序字段建联合索引(必须是
INDEX(created_at, id),顺序不能反) - 在 WHERE 中混用其他过滤条件却没包含在索引里,导致索引失效
- 游标值被前端篡改或精度丢失(比如 JS 把时间截断成秒级)
如何生成和校验游标值(cursor)
游标不是随意拼的字符串,它是排序键的编码结果。推荐做法:
- 服务端从查询结果中取最后一条的排序字段,JSON 序列化后 Base64 编码,例如:
base64_encode(json_encode(['2025-05-01 10:00:00', 12345])) - 前端只传这个字符串,后端解码后直接用于 WHERE,避免类型解析错误
- 务必校验解码后的数组长度和字段类型,防止注入或越界(如传入
["abc", null]) - 不要用主键 ID 单独做游标——如果业务允许 ID 不连续或有删改,极易漏数据
游标分页真正难的不是写 SQL,而是保证排序键稳定、索引有效、编码防篡改。一旦某条记录的 created_at 被更新,它就可能在下一页重复出现——这点容易被忽略。
# mysql
# js
# 前端
# json
# 编码
# 后端
# 为什么
# sql
# NULL
# select
# timestamp
# 字符串
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
JavaScript数据类型有哪些_如何准确判断一个变量的类型
Laravel如何优雅地处理服务层_在Laravel中使用Service层和Repository层
HTML5空格在Angular项目里怎么处理_Angular中空格的渲染问题【详解】
JavaScript常见的五种数组去重的方式
Laravel如何使用Telescope进行调试?(安装和使用教程)
米侠浏览器网页图片不显示怎么办 米侠图片加载修复
小视频制作网站有哪些,有什么看国内小视频的网站,求推荐?
如何确保西部建站助手FTP传输的安全性?
Laravel如何处理和验证JSON类型的数据库字段
深入理解Android中的xmlns:tools属性
Microsoft Edge如何解决网页加载问题 Edge浏览器加载问题修复
谷歌Google入口永久地址_Google搜索引擎官网首页永久入口
Edge浏览器提示“由你的组织管理”怎么解决_去除浏览器托管提示【修复】
javascript中闭包概念与用法深入理解
Win11怎么关闭资讯和兴趣_Windows11任务栏设置隐藏小组件
Firefox Developer Edition开发者版本入口
Laravel如何使用Guzzle调用外部接口_Laravel发起HTTP请求与JSON数据解析【详解】
Laravel与Inertia.js怎么结合_使用Laravel和Inertia构建现代单页应用
Laravel怎么实现支付功能_Laravel集成支付宝微信支付
做企业网站制作流程,企业网站制作基本流程有哪些?
网站页面设计需要考虑到这些问题
如何在VPS电脑上快速搭建网站?
JavaScript如何实现继承_有哪些常用方法
PHP 实现电台节目表的智能时间匹配与今日/明日轮播逻辑
网站制作公司哪里好做,成都网站制作公司哪家做得比较好,更正规?
Laravel怎么返回JSON格式数据_Laravel API资源Response响应格式化【技巧】
如何用AWS免费套餐快速搭建高效网站?
Laravel项目结构怎么组织_大型Laravel应用的最佳目录结构实践
如何在云虚拟主机上快速搭建个人网站?
Laravel怎么在Blade中安全地输出原始HTML内容
Android中AutoCompleteTextView自动提示
如何在阿里云服务器自主搭建网站?
魔毅自助建站系统:模板定制与SEO优化一键生成指南
北京网站制作公司哪家好一点,北京租房网站有哪些?
HTML5建模怎么导出为FBX格式_FBX格式兼容性及导出步骤【指南】
Laravel怎么实现验证码功能_Laravel集成验证码库防止机器人注册
微信小程序 wx.uploadFile无法上传解决办法
PHP正则匹配日期和时间(时间戳转换)的实例代码
安克发布新款氮化镓充电宝:体积缩小 30%,支持 200W 输出
Win11怎么修改DNS服务器 Win11设置DNS加速网络【指南】
HTML5空格和margin有啥区别_空格与外边距的使用场景【说明】
百度输入法ai面板怎么关 百度输入法ai面板隐藏技巧
网站制作报价单模板图片,小松挖机官方网站报价?
HTML5空格和nbsp有啥关系_nbsp的作用及使用场景【说明】
香港服务器网站推广:SEO优化与外贸独立站搭建策略
Laravel怎么使用Blade模板引擎_Laravel模板继承与Component组件复用【手册】
如何确认建站备案号应放置的具体位置?
香港服务器建站指南:外贸独立站搭建与跨境电商配置流程
Laravel如何使用Laravel Vite编译前端_Laravel10以上版本前端静态资源管理【教程】
Laravel如何为API生成Swagger或OpenAPI文档

