游标分页 + 数据删除:游标被删的完整解决方案
游标本身就是一条数据的唯一标识(ID / 时间戳),如果这条数据被物理删除了,游标就会失效。
但完全有成熟、简单的解决方案,不用怕删除导致分页崩掉。
一、先搞懂:游标分页的核心原理
游标分页不是靠 page=1&size=10 跳页,而是靠:
给我上一页最后一条数据的唯一标识(游标),查询比它更早 / 更新的数据。
举个例子:
- 消息列表:
id=100,99,98...1 - 第一页游标:
cursor=100 - 下一页:查
id < 100的数据
游标 = 上一页最后一条数据的 ID
二、删除游标数据会发生什么?
场景复现
- 你加载完一页,游标停在 id=95(这条消息是用户自己发的)
- 用户立刻删除了 id=95 这条消息
- 你滑动加载下一页,请求带上
cursor=95
结果
- 数据库查不到 cursor=95 这条数据
- 分页逻辑直接卡住 / 报错 / 查不出下一页
- 前端表现:加载下一页转圈圈,没数据
这就是你担心的游标被物理删除导致分页失效。
三、企业级通用解决方案:不要物理删除!
这是解决游标分页 + 删除问题的唯一标准方案:
改用【软删除】,不物理删除
- 不执行
DELETE FROM msg WHERE id=95 - 执行
UPDATE msg SET is_deleted = 1 WHERE id=95
为什么这样能解决问题?
-
游标数据还在数据库里
- 虽然用户看不见,但
id=95这条记录还存在 - 游标查询不会报错、不会断链
- 虽然用户看不见,但
-
查询时自动过滤已删除数据查询时自动过滤已删除数据
mysql
-- 游标分页正常查询,过滤删除数据
SELECT * FROM message
WHERE is_deleted = 0 -- 关键!只查未删除
AND id < #{cursor}
ORDER BY id DESC
LIMIT 20
完美效果
- 用户删除消息:前端消失、数据库保留标记
- 游标依然有效,分页完全不受影响
- 不会漏数据、不会重复、不会断页
这就是聊天、评论、信息流场景 100% 用的方案。
四、极端情况:必须物理删除,怎么办?
如果你项目强制物理删除(不推荐),也有兜底方案:
方案 1:游标不依赖最后一条数据,用【范围游标】
不用单条 ID 做游标,改用时间 / ID 区间:
- 游标:
max_id=100, min_id=80 - 下一页:
id < 80 - 就算中间删数据,不影响分页
方案 2:查询时判断游标是否存在,不存在就自动修复
后端逻辑:
- 接收前端传来的 cursor
- 先查该数据是否存在
- 不存在就找离它最近的有效数据作为新游标
- 继续分页
伪代码:
js
let cursor = req.query.cursor;
// 检查游标是否存在
const exist = await db.find({ id: cursor });
if (!exist) {
// 自动找最近的有效数据作为新游标
cursor = await db.findOne({
where: { id < cursor, is_deleted:0 },
order: { id: 'desc' }
}).id;
}
// 正常分页查询
五、总结:游标分页+删除的最佳实践
| 问题 | 解决方案 | 效果 |
|---|---|---|
| 游标数据被物理删除 | 禁止物理删除 | 分页失效 |
| 游标数据被软删除 | is_deleted 标记 | 分页完全正常 |
| 必须物理删除 | 自动修复游标 | 兼容但复杂 |
最终结论
- 用户删除消息,一定会导致游标对应数据消失
- 软删除(is_deleted)是唯一简单、稳定、通用的解法
- 聊天、消息流场景必须用软删除,否则分页一定会出问题