OFFSET深翻变慢是因为MySQL需真实扫描并丢弃前10万行,I/O与CPU开销线性增长;应改用游标分页,配合唯一排序字段、覆盖索引、Redis缓存及严格分页限制。

为什么 OFFSET 深翻会变慢
MySQL 执行 OFFSET 100000 LIMIT 20 时,并不会跳过前 10 万行索引节点,而是真实扫描并丢弃它们。I/O 和 CPU 开销随偏移量线性增长,5000 页(每页 20 条)≈ 扫描 10 万行。这不是 Golang 层能绕过的,database/sql 只是忠实地执行 SQL —— 问题在查询逻辑本身。
游标分页必须用 WHERE + 排序字段组合
不能只靠 WHERE created_at ,时间可能重复;必须加二级排序字段兜底,例如 <code>id:
WHERE created_at 是常见错误写法,会导致漏数据- 正确写法是:
WHERE created_at 配合 <code>ORDER BY created_at DESC, id DESC - 索引必须覆盖两个字段:
INDEX(created_at, id),顺序不能颠倒 - 前端传入的游标值,应是上一页最后一条记录的
created_at和id,不是第一条
GORM 中游标分页容易忽略的三件事
很多人直接链式调用 Where().Order().Limit(),但以下三点不处理就会静默出错:
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
-
Where()参数必须预处理,禁止字符串拼接游标值,否则有 SQL 注入风险 -
Find()返回的是*gorm.DB,错误藏在Error字段里,不显式检查就可能返回空 slice 而不报错 -
Count()在游标模式下毫无意义,别再查 total —— 改用LIMIT size + 1判断是否有下一页,响应里只返回has_next: true/false
搜索场景下仍需保留 page/size 的工程妥协
用户要“跳到第 87 页”或后台导出列表时,游标分页无法满足。此时只能做缓解,而非根治:
立即学习“go语言免费学习笔记(深入)”;
- 限制最大页码:
if page > 2000 { return ErrPageTooFar },前端禁用跳转输入框 - 对高频页(如前 500 页)用 Redis 缓存
page:123 → json,避免反复查库 - 用覆盖索引减少回表,例如只查
SELECT id, title FROM posts WHERE ... ORDER BY id LIMIT ? OFFSET ? - 绝不允许
size > 100,防止单次拉取过多数据压垮内存和网络

















