这是一种基于排序字段值而不是偏移量的分页方法。
核心思想
记住上一页最后一条记录的位置,然后从这个位置开始查询下一页
逐步分解
第一页查询(基础查询)
select * from loc_common_info where id not in (1,2,3) -- 假设用户去过场地1,2,3 and status = 'active' order by popularity_score desc, id desc limit 3; -- 假设每页3条
假设返回结果:
id | name | popularity_score |
---|---|---|
10 | 场地a | 95 |
8 | 场地b | 95 |
5 | 场地c | 90 |
关键点:我们记录最后一条记录的值:
lastscore = 90
(id=5的popularity_score)lastid = 5
(最后一条记录的id)
第二页查询(使用游标)
select * from loc_common_info where id not in (1,2,3) and status = 'active' -- 关键条件:找到排在"最后一条记录"之后的所有记录 and (popularity_score < #{lastscore} or (popularity_score = #{lastscore} and id < #{lastid})) order by popularity_score desc, id desc limit 3;
条件逻辑详解
条件分解
and ( popularity_score < 90 or (popularity_score = 90 and id < 5) )
这个条件的意思是:
popularity_score < 90
:
- 找到所有评分严格小于90的记录
- 这些记录自然排在评分90的记录后面
popularity_score = 90 and id < 5
:
- 找到评分等于90,但id更小的记录
- 因为排序是
popularity_score desc, id desc
,所以id小的排在后面
实际数据示例
假设数据库中有这些数据:
id | popularity_score |
---|---|
10 | 95 |
8 | 95 |
5 | 90 |
3 | 90 |
7 | 85 |
2 | 85 |
4 | 80 |
第二页查询条件:(score < 90) or (score = 90 and id < 5)
符合条件的记录:
- id=3:满足
score = 90 and id < 5
- id=7:满足
score < 90
- id=2:满足
score < 90
- id=4:满足
score < 90
排序后第二页结果:
id | popularity_score |
---|---|
3 | 90 |
7 | 85 |
2 | 85 |
为什么需要复合排序order by score desc, id desc
如果只按 popularity_score desc
排序:
问题场景:多个记录有相同的popularity_score
第一页:id=10(95), id=8(95), id=5(90) ← 最后一条id=5, score=90
第二页应该显示:id=3(90), id=7(85), id=2(85)
但如果只按score排序,数据库不保证相同score的记录顺序稳定,可能导致:
- 第一次查询:id=10(95), id=8(95), id=5(90)
- 第二次查询:id=10(95), id=8(95), id=3(90) ← 顺序变了!
复合排序确保:
- 主要按popularity_score降序
- score相同时,按id降序,保证顺序绝对稳定
前端-后端交互流程
// 第一页请求 pagerequest request1 = new pagerequest(0, 3, null); // 没有游标 pageresult result1 = service.getrecommendations(request1); // 返回结果包含下一页游标 string nextcursor = result1.getnextcursor(); // 编码为: "90_5" // 第二页请求 pagerequest request2 = new pagerequest(0, 3, "90_5"); // 使用游标 pageresult result2 = service.getrecommendations(request2);
与传统offset分页对比
offset分页(有问题)
-- 第一页 select ... limit 0, 3; -- 返回记录1,2,3 -- 第二页 select ... limit 3, 3; -- 返回记录4,5,6
问题:如果第一页和第二页之间有新数据插入,会导致:
- 重复记录(新数据挤占了位置)
- 丢失记录(原有记录被挤到后面)
游标分页(稳定)
-- 第一页:返回id=10,8,5,记住lastscore=90, lastid=5 -- 第二页:查询score<90 or (score=90 and id<5)的记录
优势:
- 不受数据增删影响
- 性能更好(不需要计算offset)
- 顺序绝对稳定
总结
游标分页的核心就是:记住上一页的终点,从终点开始找下一页,通过复合排序和精确的条件定位,确保分页的准确性和稳定性。
游标对象设计
@data public class recommendationcursor { private string type; // "new", "rotate", "retain" private double lastscore; // 上一页最后一条的popularity_score private long lastid; // 上一页最后一条的id private integer page; // 当前页码 private string randomseed; // 随机种子 public static recommendationcursor parsecursor(string cursorstr, string type) { if (cursorstr == null) { return new recommendationcursor(type, null, null, 0, generaterandomseed()); } // 解析游标逻辑... } public string tocursorstring() { // 生成游标字符串逻辑... } }
关键要点
- 不要使用 offset:limit offset, count 在数据量大时性能差且不稳定
- 游标分页:适合主内容区,保证稳定性和性能
- 随机分页:适合轮换区,增加多样性
- 复合排序:order by score desc, id desc 确保排序稳定
- 状态保持:通过游标或种子保持分页状态
这样设计后,每次分页查询都能返回不同的内容,同时保证性能和稳定性。
到此这篇关于mysql实现游标分页的方法详解的文章就介绍到这了,更多相关mysql游标分页内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论