news 2026/7/2 13:34:01

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/7/1 15:52:59

OpenAI gpt-oss-20b支持13万token长上下文

OpenAI gpt-oss-20b支持13万token长上下文 [【免费下载链接】gpt-oss-20b gpt-oss-20b —— 基于OpenAI开源权重的轻量级高性能语言模型&#xff08;21B参数&#xff0c;3.6B活跃参数&#xff09; 项目地址: https://ai.gitcode.com/hf_mirrors/openai/gpt-oss-20b](https://…

作者头像 李华
网站建设 2026/7/2 1:40:25

文件清单提取

背景 在现代企业数据管理和文件整理工作中&#xff0c;经常需要对大量文件进行统计分析、归档整理和批量处理。传统的手动整理方式效率低下&#xff0c;难以应对复杂的文件组织结构&#xff0c;且容易出错。特别是在需要生成文件清单报表、进行文件分类统计时&#xff0c;人工…

作者头像 李华
网站建设 2026/7/1 20:04:03

淘宝秒杀系统架构实战 - 百万级并发技术方案

一、业务场景分析1.1 秒杀特点瞬时流量: 开场10秒内100万请求读写比例: 1000:1 (99.9%用户抢不到)库存稀缺: 1000件商品,100万人抢强一致性: 不能超卖,不能少卖用户体验: P99延迟 < 200ms1.2 核心技术挑战100万并发 ↓网关层(5万) 应用层(2万) 数据层(1万)如何削峰? 如何防…

作者头像 李华
网站建设 2026/7/2 8:09:12

kotaemon本地化隐私保护方案详解

Kotaemon本地化隐私保护方案详解 在AI技术加速渗透企业核心业务的当下&#xff0c;一个尖锐的问题摆在开发者面前&#xff1a;如何在享受大模型智能红利的同时&#xff0c;守住数据安全的生命线&#xff1f;尤其对于金融、医疗等敏感行业&#xff0c;哪怕是最细微的数据外泄风险…

作者头像 李华
网站建设 2026/7/1 20:12:45

Python爬虫实战:基于异步技术的大宗商品期货交易数据爬取与趋势分析

引言:期货数据爬虫的重要性与挑战 在当今数字化金融时代,期货市场交易数据已成为投资者、分析师和研究人员进行大宗商品价格趋势分析的关键资源。期货数据不仅反映了市场供需关系,还包含了宏观经济、政策变化和全球事件的影响。然而,获取高质量、实时的期货交易数据面临着…

作者头像 李华
网站建设 2026/7/1 20:15:00

46、Linux使用指南:从基础到高级的全面攻略

Linux使用指南:从基础到高级的全面攻略 一、Linux基础概念 1.1 “Free”的含义 在特定语境中,“free”指的是自由或自主,而非价格层面的免费。这种区别在相关介绍中会有详细解释。 1.2 Unix的起源 “Unix”最初写作“Unics”,代表“Uniplex Information and Computing…

作者头像 李华