原生 Row 方法不支持分页,因其仅返回单行 *sql.Row 且无 Limit/Offset 逻辑;分页必须用 Rows() 配合手动迭代与 Scan,字段顺序和别名须严格匹配;推荐封装安全函数并优先使用 GORM 链式 API。

原生 Row 方法本身不支持分页
直接调用 db.Raw("SELECT ...").Row() 只能查单行,它返回的是 *sql.Row,没有 Limit、Offset 或分页逻辑。想靠它“执行分页”属于误解——Row() 是为单记录设计的,强行塞 LIMIT 1 OFFSET 100 语法虽能跑,但结果不可控(比如没数据时 panic,或类型扫描错位)。
要用原生 SQL 分页,必须用 Rows() + 手动处理
Rows() 返回 *sql.Rows,支持迭代,这才是分页查询的正确入口。常见错误是写完 Raw().Rows() 后直接 Scan 到结构体切片里,但 GORM 不自动做批量映射,得自己循环处理。
- 必须显式调用
rows.Next()和rows.Scan(),不能跳过游标移动 - 字段顺序必须和 SQL 中
SELECT的列严格一致,否则Scan报sql: expected 3 destination arguments in Scan, not 2 - 如果用了
JOIN或别名,结构体字段名要匹配别名(如SELECT u.name AS user_name→ 结构体字段需为UserName string) - 推荐封装成函数:接收
*gorm.DB、SQL 字符串、参数列表、目标切片指针,内部做rows.Close()defer 防泄漏
为什么不用 Row() 拼分页?三个硬伤
有人试过 db.Raw("SELECT * FROM users LIMIT 1 OFFSET ?", offset).Row() 模拟“第 N 页第一条”,这在逻辑上就错了:
-
Row()设计就是取**最多一行**,哪怕 SQL 写了LIMIT 10,它也只读第一个,其余丢弃 - 若 offset 超出范围(如表只有 50 行却
OFFSET 100),Row()返回nil,但Scan()会 panic:sql: no rows in result set - 无法获取总数,也没法判断是否还有下一页——而分页的核心恰恰是边界控制
真正该用什么替代原生 Row 做分页?
除非你明确需要绕过 GORM 的所有抽象(比如用窗口函数、CTE 或自定义游标逻辑),否则优先走 GORM 原生链式 API:
- 简单分页:
db.Limit(20).Offset(40).Order("id ASC").Find(&users),注意必须带Order - 大数据量分页:改用游标分页,
db.Where("id > ?", lastID).Order("id ASC").Limit(20).Find(&users) - 复杂关联分页:先用
db.Session(&gorm.Session{NewDB: true})隔离会话,再手写子查询算总数,避免Count()错误继承条件
原生 Row 的定位很窄:查配置项、聚合值(如 COUNT(*))、或唯一主键校验。把它拖进分页场景,等于把螺丝刀当锤子用——不是不能敲,是敲完满地螺丝钉,还找不到哪颗该拧回去。











