news 2026/3/27 17:46:53

SQL深度分页问题案例实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SQL深度分页问题案例实战

文章目录

  • 概述
    • 对比
    • 工作原理
    • 性能对比
      • 查询性能对比
      • 数据库负载对比
  • 代码示例
    • 传统分页示例
        • 请求
        • 响应
        • SQL执行
    • 游标分页示例
        • 首次请求(无游标)
        • 响应
        • 后续请求(使用游标)
        • SQL执行
    • 游标分页最佳实践
    • 总结
      • 选择建议

概述

对比

特性传统分页游标分页
定义使用 OFFSET 和 LIMIT 参数,通过跳过前面的记录来获取指定页的数据。使用一个游标(cursor)来标记当前位置,基于游标位置获取后续数据。
特点1. 需要知道总记录数(COUNT查询)
2. 使用页码(page)和每页数量(size)
3. 可以跳转到任意页面
1. 不需要总记录数
2. 使用游标(cursor)和每页数量(size)
3.只能顺序翻页,不能跳转
优点1. 可以跳转页面:用户可以直接跳转到第N页
2. 显示总数:可以显示总记录数和总页数
3. 实现简单:逻辑直观,易于理解
4. 兼容性好:所有数据库都支持OFFSETLIMIT
1.性能优秀:
+ 不需要COUNT(*)查询
+ 查询速度稳定,不受数据量影响
+ 使用索引高效定位
2.数据一致性:
+ 基于游标位置查询,不受数据变化影响
+ 不会出现重复或遗漏数据
3.资源消耗低:
+ 不需要统计总数
+ 查询效率高
缺点1.性能问题
+COUNT(*)查询在大数据量下很慢
+OFFSET越大,查询越慢(需要跳过更多记录)
2.数据一致性问题
+ 在翻页过程中,如果有数据新增或删除,可能导致:
- 重复数据(同一数据出现在两页)
- 遗漏数据(某些数据永远不会被看到)
1. 不能跳转页面:只能顺序翻页,不能直接跳转到第N页
2. 显示总数:无法显示总记录数和总页数
3. 实现复杂:需要处理游标编码/解码
4. 游标管理:需要确保游标的唯一性和稳定性
应用场景1.需要显示总数和总页数
+ 商品列表需要显示"共1000件商品"
+ 订单列表需要显示"共50页"
2.需要跳转页面
+ 用户可以输入页码跳转
+ 需要显示页码导航(1, 2, 3…)
3.数据量不大
+ 数据量在10万以内
+ 查询频率不高
4.管理后台
+ 管理员需要查看总数
+ 需要跳转到指定页面
1.大数据量场景
+ 数据量超过10万条
+ 需要高性能查询
2.移动端列表
+ 无限滚动加载
+ 不需要显示总数
3.实时性要求高
+ 数据频繁变化
+ 需要保证数据一致性
4.C端应用
+ 用户主要浏览最新数据
+ 不需要跳转到历史页面
5.时间线/动态流
+ 微博、朋友圈等时间线
+ 订单列表(按时间排序)

工作原理

-- 先查询总数SELECTCOUNT(*)FROMtrade_orderWHEREuser_id='xxx';-- 第一页(page=1, size=10)SELECT*FROMtrade_orderWHEREuser_id='xxx'ORDERBYcreate_timeDESCLIMIT10OFFSET0;-- 第二页(page=2, size=10)SELECT*FROMtrade_orderWHEREuser_id='xxx'ORDERBYcreate_timeDESCLIMIT10OFFSET10;

执行流程:

  1. 执行COUNT(*)查询获取总记录数
  2. 根据页码计算OFFSET = (page - 1) * size
  3. 执行主查询,跳过OFFSET条记录
  4. 返回当前页数据和总数
-- 第一页(无游标)SELECT*FROMtrade_orderWHEREuser_id='xxx'ORDERBYcreate_timeDESC,idDESCLIMIT11;-- 查询11条,用于判断是否有更多数据-- 第二页(使用游标)SELECT*FROMtrade_orderWHEREuser_id='xxx'AND(create_time<'2025-12-16 10:00:00'OR(create_time='2025-12-16 10:00:00'ANDid<'xxx-uuid'))ORDERBYcreate_timeDESC,idDESCLIMIT11;

执行流程:

  1. 如果有游标,解码游标获取createTimeid
  2. 添加游标条件:create_time < cursor.createTime OR (create_time = cursor.createTime AND id < cursor.id)
  3. 查询size + 1条数据(多查1条用于判断是否有更多数据)
  4. 如果返回size + 1条,说明还有更多数据,返回前size条并生成下一个游标
  5. 如果返回 ≤size条,说明没有更多数据

性能对比

查询性能对比

说明:

  • 传统分页的COUNT(*)查询时间随数据量线性增长
  • 传统分页的OFFSET越大,查询越慢
  • 游标分页性能稳定,不受数据量和页码影响

数据库负载对比

操作传统分页游标分页
每次查询SQL数量2条(COUNT + SELECT)1条(SELECT)
COUNT查询需要全表扫描或索引扫描不需要
OFFSET操作需要跳过N条记录不需要
索引利用部分利用完全利用

代码示例

传统分页示例

请求
POST /api-portal/trade/order/page { "page": 2, "size": 10, "status": 10, "createTime": ["2025-11-16 00:00:00", "2025-12-16 00:00:00"] }
响应
{ "code": 0, "data": { "list": [...], "total": 1000, "page": 2, "size": 10, "pages": 100 } }
SQL执行
-- 1. 查询总数 SELECT COUNT(*) FROM trade_order WHERE user_id = 'xxx' AND status = 10 AND create_time BETWEEN '2025-11-16' AND '2025-12-16'; -- 2. 查询数据 SELECT * FROM trade_order WHERE user_id = 'xxx' AND status = 10 AND create_time BETWEEN '2025-11-16' AND '2025-12-16' ORDER BY create_time DESC LIMIT 10 OFFSET 10;

游标分页示例

首次请求(无游标)
POST /api-portal/trade/order/cursor-page { "size": 10, "status": 10, "createTime": ["2025-11-16 00:00:00", "2025-12-16 00:00:00"] }
响应
{ "code": 0, "data": { "list": [...], "nextCursor": "MjAyNS0xMi0xNlQxMDowMDowMHw2ZTdhNTVlYi0zMzc0LTRjMDYtYmEzZi1mZGUwMmU5MGU5MWU=", "hasMore": true } }
后续请求(使用游标)
POST /api-portal/trade/order/cursor-page { "cursor": "MjAyNS0xMi0xNlQxMDowMDowMHw2ZTdhNTVlYi0zMzc0LTRjMDYtYmEzZi1mZGUwMmU5MGU5MWU=", "size": 10, "status": 10, "createTime": ["2025-11-16 00:00:00", "2025-12-16 00:00:00"] }
SQL执行
-- 查询 size + 1 条数据 SELECT * FROM trade_order WHERE user_id = 'xxx' AND status = 10 AND create_time BETWEEN '2025-11-16' AND '2025-12-16' AND (create_time < '2025-12-16 10:00:00' OR (create_time = '2025-12-16 10:00:00' AND id < 'xxx-uuid')) ORDER BY create_time DESC, id DESC LIMIT 11;

⚠️需要注意的问题:

  1. 游标设计
  • 游标必须唯一且稳定(使用createTime + id组合)
  • 游标字段必须有索引
  • 使用 Base64 编码保护游标
  1. 排序字段
  • 必须使用唯一字段作为排序依据(如id
  • 避免使用可能重复的字段(如createTime单独排序)
  1. 游标失效
  • 如果数据被删除,游标可能失效
  • 需要处理游标解析失败的情况
  1. 关键字查询
  • JOIN 查询时需要注意性能
  • 使用DISTINCT去重

游标分页最佳实践

推荐做法:

  1. 游标格式
// 使用 createTime|id 格式,Base64编码 cursor = Base64.encode("2025-12-16T10:00:00|uuid-string")
  1. 排序规则
ORDER BY create_time DESC, id DESC -- 确保排序的唯一性和稳定性
  1. 游标条件
WHERE (create_time < cursor.createTime OR (create_time = cursor.createTime AND id < cursor.id))
  1. 判断是否有更多数据
// 查询 size + 1 条 List<Order> orders = query(size + 1); boolean hasMore = orders.size() > size; if (hasMore) { orders = orders.subList(0, size); nextCursor = createCursor(orders.get(size - 1)); }

总结

选择建议

场景推荐方案原因
移动端列表(无限滚动)游标分页性能好,数据一致
管理后台(需要总数)传统分页需要显示总数和跳转
大数据量(>10万)游标分页性能优势明显
小数据量(<10万)传统分页实现简单
实时数据流游标分页数据一致性好
需要跳转页面传统分页游标分页不支持
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/23 12:49:15

0.5B参数超越大模型:KaLM-Embedding-V2.5重塑轻量级标准

PyTorch-CUDA 基础镜像 v2.5&#xff1a;让开发者专注模型&#xff0c;让环境自己跑起来 你有没有经历过这样的场景&#xff1f;凌晨两点&#xff0c;实验马上要跑通&#xff0c;结果 pip install torch 卡在编译 cuDNN 的环节&#xff1b;或者刚在服务器上配置好环境&#xf…

作者头像 李华
网站建设 2026/3/24 7:08:45

Dify变量注入实现上下文安全传递

Dify变量注入实现上下文安全传递 你有没有遇到过这种情况&#xff1a;销售同事刚上传了一份客户合同&#xff0c;想确认交付周期&#xff0c;结果AI助手却引用了HR部门的薪酬调整通知&#xff1f;或者更糟——某个临时外包人员无意中查到了本不该看到的财务预算表。 这听起来像…

作者头像 李华
网站建设 2026/3/26 9:11:42

零代码基础也能上手!LobeChat一键部署教程(含GPU加速)

零代码基础也能上手&#xff01;LobeChat一键部署教程&#xff08;含GPU加速&#xff09; 在AI助手逐渐成为日常工具的今天&#xff0c;你是否也想过拥有一个完全属于自己的、能离线运行、响应飞快、还不会泄露隐私的智能聊天机器人&#xff1f;不需要买云服务API额度&#xf…

作者头像 李华
网站建设 2026/3/21 21:37:19

FPGA 和 IC 岗位前景、薪资对比

手把手教你如何选岗位&#xff0c;而不是被岗位选最近几年&#xff0c;很多电子相关专业的同学&#xff0c;在择业时都会卡在同一个问题上&#xff1a;FPGA 要不要转 IC&#xff1f; IC 现在是不是更有“钱途”&#xff1f; FPGA 是不是天花板太低了&#xff1f;这些问题本身没…

作者头像 李华
网站建设 2026/3/24 13:06:54

DiskInfo下载官网替代方案:高效获取YOLO资源

DiskInfo下载官网替代方案&#xff1a;高效获取YOLO资源 在工业视觉系统部署现场&#xff0c;你是否曾经历过这样的场景&#xff1f;产线调试进入关键阶段&#xff0c;工程师准备加载最新的 YOLOv8 模型进行缺陷检测测试&#xff0c;却发现 yolov8n.pt 文件从 GitHub 下载速度…

作者头像 李华