后端-数据库-分页offset漂移导致文章跨页重复
问题现象
- 网页新闻列表第一页和第二页出现相同文章
- 比如某篇文章同时出现在 page=1 和 page=2 中
- 数据库本身无重复标题(817 条记录,duplicate titles: 0)
核心问题
三个因素叠加导致分页漂移:
1. 排序键不稳定(主要根因)
- 原排序:ORDER BY collected_at DESC, id DESC
- collected_at 在每次爬虫插入时刷新,同一个爬虫批次内所有文章共�享相同 collected_at(秒级精度)
- 爬虫在用户浏览期间插入新数据时,旧文章的排序位置被�挤偏移
2. COUNT 和 SELECT 非原子(次要根因)
- 两�次独立的 await session.execute() 调用,中间可能穿插爬虫写入
- 代码位置:modules/web-crawler/service.py:147-153
total_result = await session.execute(count_stmt) # 快照 A
# …此时爬虫可能插入新数据…
result = await session.execute(stmt) # 快照 B
3. Offset 分页天然缺陷(机制根因)
- OFFSET 20 的语义是"跳过前 20 行",数据变化后指向不同行
- 例:请求 page=1 返回 ID 1-20;爬虫插入 5 条新记录后排在最前;请求 page=2 (OFFSET 20) 时 ID 16-20 重复出现
解决方法
- 排序键改为不可变的
id DESC: id自增、不可变、单调递增 → offset 永不漂移-
id 本身按插入时间自然有序,等效于原来的
collected_at DESC, id DESC -
响应加入
next_cursor支持 keyset 分页: modules/web-crawler/service.py:170— 满页且有更多数据时返回items[-1].idsrc/shared/response.py:20—PaginatedData新增next_cursor: str | None-
modules/web-crawler/frontend/api.ts— 类型新增next_cursor?: string -
涉及文件:
modules/web-crawler/service.py— 核心修复modules/web-crawler/routes.py:163— 转发 next_cursorsrc/shared/response.py:20— Schema 扩展modules/web-crawler/frontend/api.ts— 前端类型
下次防错措施
- 快速定位:服务器执行
ssh news "cd /www/news && venv/bin/python -c \"..."检查重复标题 - 代码规范:分页查询统一使用不可变键(
id或created_at)排序,禁止用可能变化的字段做主排�序键 - 巡检建议:在
scripts/下增加分页一致性检测脚本,随机抽查相邻页是否有 id 重叠