vault backup: 2026-05-06 13:42:02
This commit is contained in:
+148
-6
@@ -1,5 +1,5 @@
|
||||
---
|
||||
tags: [GORM, Go, ORM, CRUD, 增删改查, First, Find, Create, Update, Delete]
|
||||
tags: [GORM, Go, ORM, CRUD, 增删改查, First, Find, Create, Update, Delete, Select, Omit, Count, Distinct, Raw SQL, Or]
|
||||
create time: 2026-04-28 00:00
|
||||
---
|
||||
|
||||
@@ -18,7 +18,7 @@ CRUD(Create / Read / Update / Delete)是最基础的数据库操作。GORM
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Start[db.Model(&User{})] --> Choice{选择方法}
|
||||
Start["db.Model()<br/>指定数据模型"] --> Choice{选择方法}
|
||||
Choice --> |单条| Single[First / Take / Last]
|
||||
Choice --> |批量| Batch[Find]
|
||||
|
||||
@@ -127,10 +127,152 @@ var roles []string
|
||||
db.Model(&User{}).Pluck("DISTINCT role", &roles)
|
||||
```
|
||||
|
||||
> [!question] 思考题
|
||||
> `Scan` 和 `Find` 有什么区别?什么时候该用哪个?
|
||||
>
|
||||
> > **答案**:`Find` 返回模型切片,适合后续还要调用 GORM 方法;`Scan` 可以映射到任意结构体甚至非结构体变量,更灵活但失去了 GORM 的类型安全保护。
|
||||
### Or —— 条件"或"组合
|
||||
|
||||
`Where` 默认是"与"关系。当需要多个条件**只要满足其一即可**时,使用链式 `Or`:
|
||||
|
||||
```go
|
||||
// 查找名字为 "Alice" 或者角色为 "admin" 的用户
|
||||
db.Where("name = ?", "Alice").Or("role = ?", "admin").Find(&users)
|
||||
// SELECT * FROM users WHERE name = 'Alice' OR role = 'admin';
|
||||
|
||||
// 多组"或"嵌套 —— 用括号包裹
|
||||
db.Where("role = ?", "admin").
|
||||
Where(db.Or("status = ?", "active").Or("status = ?", "super")).
|
||||
Find(&users)
|
||||
// SELECT * FROM users WHERE role = 'admin' AND (status = 'active' OR status = 'super');
|
||||
```
|
||||
|
||||
> [!warning] Or 和 Where 的顺序很重要
|
||||
> GORM 将 `Or` 附加到**前一个 Where 所在的逻辑组**。先写 `Where + Or` 再套 `Where`,得到的是 `A OR B AND C` 而非 `(A OR B) AND C`。如果你期望后面的括号效果,请用 `db.Or()` 显式包裹。
|
||||
|
||||
### Raw SQL —— 原生 SQL 查询与执行
|
||||
|
||||
有些场景 GORM 无法优雅表达(如复杂子查询、存储过程调用),这时可以退回到原生 SQL。GORM 提供了两类 API:
|
||||
|
||||
| 方法 | 用途 | 返回 |
|
||||
|------|------|------|
|
||||
| `Raw(sql, args...)` | 构造原生查询,仍可链式调 Select/Where 等 | `*gorm.DB` |
|
||||
| `Exec(sql, args...)` | 直接执行 DML/DDL,不返回数据行 | `sql.Result` |
|
||||
|
||||
```go
|
||||
// 1. Raw — 原生 SQL + GORM 链式方法混搭
|
||||
var results []User
|
||||
db.Raw("SELECT * FROM users WHERE created_at > ?", time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)).
|
||||
Order("id DESC").
|
||||
Find(&results)
|
||||
// 用原生 SQL 写初始过滤条件,再用 GORM 链做排序和映射
|
||||
|
||||
// 2. Exec — 执行无返回结果的语句(INSERT/UPDATE/DELETE/DDL)
|
||||
db.Exec("DELETE FROM expired_sessions")
|
||||
db.Exec("ALTER TABLE orders ADD COLUMN remark TEXT")
|
||||
|
||||
// 3. 带参数绑定的原生查询(防 SQL 注入)
|
||||
var count int
|
||||
db.Raw("SELECT COUNT(*) FROM users WHERE status = ?", "active").Scan(&count)
|
||||
fmt.Println("活跃用户数:", count)
|
||||
```
|
||||
|
||||
> [!tip] 为什么要用参数绑定而不是字符串拼接?
|
||||
> ```go
|
||||
> // ❌ 危险 — 字符串拼接可被 SQL 注入攻击
|
||||
> db.Raw("SELECT * FROM users WHERE name = '" + input + "'")
|
||||
>
|
||||
> // ✅ 安全 — 参数化查询由驱动层转义
|
||||
> db.Raw("SELECT * FROM users WHERE name = ?", input)
|
||||
> ```
|
||||
|
||||
### Rows —— 游标式逐行处理
|
||||
|
||||
对于超大数据集,一次性加载所有结果到内存会导致 OOM。此时可以用 `Rows()` 返回标准库的 `*sql.Rows`,逐行迭代:
|
||||
|
||||
```go
|
||||
rows, err := db.Table("logs").Where("level = ?", "error").Rows()
|
||||
if err != nil { ... }
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var id uint
|
||||
var msg string
|
||||
rows.Scan(&id, &msg) // 手动扫描字段
|
||||
processError(id, msg) // 自定义处理逻辑
|
||||
}
|
||||
```
|
||||
|
||||
> [!question] 什么时候该用 Rows 而不是 Find?
|
||||
> - **数据量极大**(百万级以上)→ `Rows()` 逐行消费,内存可控
|
||||
> - **每行处理成本高** → 避免全部加载后再逐个处理
|
||||
> - **普通场景(几千条以内)** → `Find()` 更简洁,不用手动管理 `Close()`
|
||||
|
||||
## 字段选择查询
|
||||
|
||||
掌握了如何查数据后,接下来思考一个问题:**每次全量读取整张表真的是最高效的方式吗?**
|
||||
|
||||
显然不是——只需要的字段才去读,既能节省网络带宽,也能减少序列化的开销。
|
||||
|
||||
### Select — 指定要查哪些列
|
||||
|
||||
```go
|
||||
// 查 name 和 email 两个字段,扫描到完整模型(仅加载部分字段)
|
||||
type UserLite struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
Name string `gorm:"size:64"`
|
||||
Email string `gorm:"size:128"`
|
||||
}
|
||||
|
||||
var users []UserLite
|
||||
db.Model(&User{}).Select("id, name, email").Find(&users)
|
||||
// SELECT id, name, email FROM users;
|
||||
|
||||
// 结合 Pluck 提取单一列
|
||||
var emails []string
|
||||
db.Model(&User{}).Pluck("email", &emails)
|
||||
// SELECT email FROM users; -> []string{"alice@example.com", ...}
|
||||
```
|
||||
|
||||
### Omit — 排除某些列
|
||||
|
||||
与 `Select` 相反,`Omit` 告诉 GORM **不要读取**这些字段,适合大文本字段(如 `content`、`bio`)不需要频繁读取的场景:
|
||||
|
||||
```go
|
||||
// 查除了 content 和 password 之外的所有字段
|
||||
db.Model(&Article{}).Omit("content", "password").Find(&articles)
|
||||
// SELECT id, title, author_id, created_at FROM articles;
|
||||
```
|
||||
|
||||
> [!tip] `Omit` vs `Select` 的选择策略
|
||||
> - 需要的列少(比如 3 个字段中有 20 个)→ `Omit` 更省事
|
||||
> - 需要的列多 → `Select` 更清晰
|
||||
> - 敏感字段(密码、内部密钥)永远建议 `Omit`,以防误暴露
|
||||
|
||||
### Distinct — 去重查询
|
||||
|
||||
```go
|
||||
// 获取所有不重复的角色
|
||||
var roles []string
|
||||
db.Model(&User{}).Distinct("role").Pluck("role", &roles)
|
||||
// SELECT DISTINCT role FROM users; -> ["admin", "user", "editor"]
|
||||
|
||||
// 组合使用 — 去重 + 排序
|
||||
db.Model(&Order{}).Distinct().Order("created_at DESC").Find(&orders)
|
||||
// SELECT DISTINCT * FROM orders ORDER BY created_at DESC;
|
||||
```
|
||||
|
||||
### Count — 计数查询
|
||||
|
||||
```go
|
||||
// 统计总行数
|
||||
var total int64
|
||||
db.Model(&User{}).Count(&total)
|
||||
// SELECT COUNT(*) FROM users; -> total = 1523
|
||||
|
||||
// 配合条件统计
|
||||
db.Model(&User{}).Where("status = ?", "active").Count(&total)
|
||||
// SELECT COUNT(*) FROM users WHERE status = 'active';
|
||||
```
|
||||
|
||||
> [!tip] Count 为什么返回值是 `int64`?
|
||||
> MySQL 的 `COUNT()` 函数在结果超过 2^31 时可能超出 `int32` 范围,Go 中 `int` 在 32 位系统是 32 位,所以 GORM 统一返回 `int64` 保证安全。
|
||||
|
||||
## 创建(Create)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user