gorm分表操作

gorm分页操作

一.分页概述


一、为什么要分页?

  • 查询结果太多时,不可能一次性全查出来。
  • 分页的作用:控制数据量(每页多少条),并能跳到指定页。
  • 在 SQL 中分页主要靠 LIMIT + OFFSET(或 MySQL 8+ 的 LIMIT ... OFFSET / ROW_NUMBER 窗口函数)。

二、GORM 分页的常见写法

1. 基本分页

1
2
3
4
5
6
7
8
9
10
type User struct {
ID uint
Name string
}

var users []User
page := 2 // 页码(从 1 开始)
pageSize := 10 // 每页 10 条

db.Limit(pageSize).Offset((page - 1) * pageSize).Find(&users)

对应 SQL(查询第 2 页,每页 10 条):

1
SELECT * FROM users LIMIT 10 OFFSET 10;

2. 带排序的分页

分页查询通常要加排序,否则不同页之间可能会重复/丢失数据:

1
db.Order("id DESC").Limit(pageSize).Offset((page - 1) * pageSize).Find(&users)

3. 带总数统计(常用)

分页通常需要总记录数(前端显示“共 N 页”)。

1
2
3
4
5
6
7
8
9
10
11
var total int64
db.Model(&User{}).Count(&total)

var users []User
db.Order("id DESC").
Limit(pageSize).
Offset((page - 1) * pageSize).
Find(&users)

// total = 总记录数
// users = 当前页数据

4. 封装成函数

可以封装成通用分页函数,避免重复写逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
type PageResult[T any] struct {
Total int64
Page int
PageSize int
Data []T
}

func Paginate[T any](db *gorm.DB, page, pageSize int, out *[]T) (*PageResult[T], error) {
if page < 1 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}

var total int64
if err := db.Count(&total).Error; err != nil {
return nil, err
}

if err := db.Limit(pageSize).Offset((page - 1) * pageSize).Find(out).Error; err != nil {
return nil, err
}

return &PageResult[T]{
Total: total,
Page: page,
PageSize: pageSize,
Data: *out,
}, nil
}

调用:

1
2
3
4
5
6
var users []User
res, _ := Paginate(db.Model(&User{}).Order("id desc"), 2, 10, &users)

fmt.Println("总数:", res.Total)
fmt.Println("当前页:", res.Page)
fmt.Println("数据:", res.Data)

5. Cursor 分页(大数据时更高效)

如果数据量非常大,OFFSET 会越来越慢(因为 MySQL 要扫描并丢弃前面的行)。
可以用 基于游标的分页,例如“取 id > 上一页最后一个 id 的数据”:

1
2
3
4
5
var users []User
lastID := 100 // 上一页最后一条记录的 id
pageSize := 10

db.Where("id > ?", lastID).Order("id ASC").Limit(pageSize).Find(&users)

优点:性能稳定,适合无限滚动、导出大数据等场景。
缺点:只能“向前翻页”,不能随便跳转到第 N 页。


三、注意事项

  1. 必须加排序,否则分页结果可能重复或缺失。
  2. Offset 的性能问题:偏移量越大越慢,适合小数据量分页。
  3. 大数据推荐用 游标分页(Cursor Pagination)
  4. 统计总数时 db.Count(&total) 要在分页前执行,否则只会统计当前页数量。

✅ 总结:

  • 小数据分页:Limit + Offset + Order
  • 需要总页数:加 Count
  • 大数据高性能:游标分页(id > lastID)。
  • 封装分页函数提高复用性。

二.offset详解


1. Offset 是什么?

在 SQL 里,OFFSET n 的意思是:跳过前 n 条记录,从第 n+1 条开始返回
搭配 LIMIT m,就是:从第 n+1 条开始,取 m 条记录


2. 举例说明

假设 users 表里有 20 条记录(id 从 1 到 20),排序是 ORDER BY id ASC

  • 第一页(page=1, pageSize=5):

    1
    SELECT * FROM users ORDER BY id ASC LIMIT 5 OFFSET 0;

    👉 跳过 0 条,从第 1 条开始取 5 条 → 返回 id = 1..5

  • 第二页(page=2, pageSize=5):

    1
    SELECT * FROM users ORDER BY id ASC LIMIT 5 OFFSET 5;

    👉 跳过前 5 条,从第 6 条开始取 5 条 → 返回 id = 6..10

  • 第三页(page=3, pageSize=5):

    1
    SELECT * FROM users ORDER BY id ASC LIMIT 5 OFFSET 10;

    👉 跳过前 10 条,从第 11 条开始取 5 条 → 返回 id = 11..15

所以 (page - 1) * pageSize 这个计算公式就是:需要跳过多少条,才能到这一页的起始位置


3. 为什么分页必须加 Order?

因为如果不加 ORDER BY,数据库返回行的顺序是不确定的。
那分页可能出现:第一页查到 id=1..5,第二页可能又包含 id=3..7,结果重复或丢数据。

所以分页时一般写成:

1
db.Order("id ASC").Limit(pageSize).Offset((page-1)*pageSize).Find(&users)

4. Offset 的性能问题

  • OFFSET n 实际上是数据库先扫描前 n 条再丢弃,然后返回后面的。
  • 当数据量很大(比如 page=100000),就要扫描很多行 → 查询变慢。
  • 解决办法:用 游标分页(cursor pagination),比如 WHERE id > lastID

✅ 总结一句:
Offset 就是“跳过多少条记录”。 在分页公式 (page-1)*pageSize 里,它确保你能跳过前面的页数,直接定位到第 N 页的第一条数据。


三.page和pageSize详解


1. page

  • 表示第几页
  • 通常从 1 开始(有些程序员喜欢从 0 开始,但主流是 1)。
  • 举例:
    • page=1 → 第一页
    • page=2 → 第二页
    • page=3 → 第三页

2. pageSize

  • 表示每一页要显示多少条记录
  • 就是分页的“容量”。
  • 举例:
    • pageSize=10 → 每页 10 条
    • pageSize=20 → 每页 20 条

3. 结合 pagepageSize 的效果

分页公式:

1
2
OFFSET = (page - 1) * pageSize
LIMIT = pageSize

假设有 users 表,id = 1..20,总共有 20 条数据:

page pageSize SQL 查询 结果 (id)
1 5 LIMIT 5 OFFSET 0 1..5
2 5 LIMIT 5 OFFSET 5 6..10
3 5 LIMIT 5 OFFSET 10 11..15
4 5 LIMIT 5 OFFSET 15 16..20
2 10 LIMIT 10 OFFSET 10 11..20
3 7 LIMIT 7 OFFSET 14 15..20(6 条)

4. 直观理解

  • page 👉 你在翻第几页
  • pageSize 👉 每页能装多少条数据
  • OFFSET 👉 跳过多少条,才能到这一页的起点

所以:

  • page 控制“翻到哪一页”
  • pageSize 控制“每页多少条”

[up主专用,视频内嵌代码贴在这]