--- tags: [GORM, Go, ORM, CRUD, 增删改查, First, Find, Create, Update, Delete] create time: 2026-04-28 00:00 --- # CRUD 操作 ## 概述 CRUD 是最基础的数据库操作。GORM 提供了语义清晰的五个核心查询方法(First / Take / Find / Get / Last)和多种更新删除 API。掌握它们就能覆盖日常 80% 的数据交互需求。 ## 五大查询方法 ```mermaid flowchart LR A[db.Model(&User{})] --> B{First / Take / Last} A --> C[Find - 批量查询] A --> D[Get - 封装版 First] B --> E["单条记录
返回 *User"] C --> F["多条记录切片
返回 []User"] D --> E style A fill:#4FC08D,color:#fff style B fill:#3B82F6,color:#fff style C fill:#8B5CF6,color:#fff style E fill:#10B981,color:#fff style F fill:#F59E0B,color:#fff style D fill:#EC4899,color:#fff ``` ### 方法对比 | 方法 | 返回类型 | 行为 | 是否需要 Where | |------|----------|------|----------------| | `First` | 单条指针 | 按主键排序取第一条,**必须有条件** | ✅ 必须 | | `Take` | 单条指针 | 随机取一条(ORDER BY 随机),**必须有条件** | ✅ 必须 | | `Last` | 单条指针 | 按主键倒序取最后一条 | ✅ 必须 | | `Find` | 切片 | 查询所有匹配行 | ❌ 可选 | | `Get` | 单条指针 | `First` 的封装,自动处理空结果 | 同 First | ### First vs Take 的区别 ```go // First: 按主键升序取第一条(确定性) db.First(&user) // SELECT * FROM users ORDER BY primary_key LIMIT 1; WHERE ? // Take: 无序取任意一条(用于随机抽样) db.Take(&user) // SELECT * FROM users LIMIT 1; ``` ### 实际用法示例 ```go // 1. First - 根据主键查找 var user User db.First(&user, 10) // WHERE id = 10 db.First(&user, "name = ?", "john") // WHERE name = 'john' // 2. Take - 随机取一条(抽奖场景常用) var product Product db.Take(&product) // 任意一条商品 // 3. Find - 批量查询 var users []User db.Where("age > ?", 18).Find(&users) // WHERE age > 18 // 4. Get - 安全版的 First err := db.First(&user).Error if errors.Is(err, gorm.ErrRecordNotFound) { // 记录不存在 } ``` > [!danger] 常见误区 > `Find` 不带 `Where` 会查询全表!数据量大时会导致 OOM。始终记得加过滤条件,或用 `Limit` 兜底。 ## 创建(Create) ```go // 单条插入 user := User{Name: "Alice", Age: 30} result := db.Create(&user) // result.RowsAffected — 影响行数 // result.Error — 错误 // 批量插入(至少两条才生效批量优化) users := []User{ {Name: "Bob", Age: 25}, {Name: "Charlie", Age: 35}, } db.Create(&users) // INSERT INTO users ... VALUES (...), (...), (...) ``` > [!tip] 批量插入上限 > GORM 内部会将批量 insert 拆分为每批约 256 条,避免单次 SQL 过大。如需调整,可通过 `db.Session(&gorm.Session{FullSaveRecords: true})` 控制行为。 ### 插入后获取自增 ID ```go db.Create(&user) fmt.Println(user.ID) // 插入后 ID 自动回填到结构体 ``` ## 更新(Update) GORM 提供多种更新粒度,从单个字段到全量替换: ### 方法速查表 | 方法 | 签名 | 行为 | |------|------|------| | `Update` | `(col string, value any)` | 更新单个字段 | | `Updates` | `(value Model | map[string]any)` | 更新一个或多个字段 | | `Save` | `(value Model)` | 全量更新所有字段 | | `UpdateColumn` | `(col string, value any)` | 同 Update 但不触发 Hooks | | `UpdateColumns` | `(value Model)` | 同 Updates 但不触发 Hooks | ### 详细示例 ```go // 1. Update - 单字段 db.Model(&user).Update("name", "Bob") // UPDATE users SET name='Bob', updated_at=... WHERE id=... // 2. Updates - 多字段(map 方式,零值也会被更新) db.Model(&user).Updates(map[string]any{ "name": "Alice", "age": 0, // 注意:0 也会被写入 "role": "admin", }) // 3. Updates - 多字段(struct 方式,只更新非零值字段!) db.Model(&user).Updates(User{Name: "Alice", Role: "admin"}) // 只有 Name 和 Role 被更新,Age 保持不变 // 4. Save - 全量覆盖(全部字段都写回去) db.Save(&user) // UPDATE users SET name='Alice', age=30, role='admin', ... WHERE id=... ``` > [!warning] Zero Values 陷阱 > `Updates` 用 struct 传入时,**零值字段("" 0 false)不会被更新**。如果需要更新零值,改用 `map[string]any` 方式: > ```go > db.Model(&user).Updates(map[string]any{"age": 0}) // ✅ 能更新为 0 > ``` ### 批量更新 ```go // 批量更新满足条件的行 db.Model(&User{}).Where("status = ?", "active").Update("role", "vip") // UPDATE users SET role='vip' WHERE status='active'; ``` ## 删除(Delete) ```go // 根据主键删除 db.Delete(&user, 10) // DELETE FROM users WHERE id = 10 // 批量删除 db.Where("age < ?", 18).Delete(&User{}) // 软删除(如果模型包含 DeletedAt) db.Delete(&user) // 不是 DELETE,而是 UPDATE ... SET deleted_at=NOW() ``` > [!info] 软删除 vs 物理删除 > 物理 `DELETE FROM` 不可逆且丢失审计痕迹。如果你启用了 `SoftDelete`(模型含 `DeletedAt` 字段),默认执行软删除。 > > 想强制执行物理删除?使用 `Unscoped`: > ```go > db.Unscoped().Delete(&user, 10) // 真正的 DELETE > ``` ## 错误处理 ```go result := db.First(&user, 100) if errors.Is(result.Error, gorm.ErrRecordNotFound) { fmt.Println("用户不存在") } else if result.Error != nil { fmt.Println("数据库错误:", result.Error) } ``` | 错误常量 | 含义 | |----------|------| | `gorm.ErrRecordNotFound` | 查询结果为空 | | `gorm.ErrInvalidData` | 插入/更新的数据无效 | | `gorm.ErrTxAlreadyCommitted` | 事务已提交 | | `gorm.ErrSavedValueNotNull` | 保存了非空字段的零值 | ## CRUD 决策流程图 ```mermaid flowchart TD A[收到数据操作请求] --> B{操作类型?} B -->|读取| C{要几条?} C -->|1 条| D{需要确定性顺序?} D -->|是| E[First - 按主键升序] D -->|否/随机| F[Take - 随机一条] C -->|N 条| G[Find + Where 条件] B -->|新增| H{单条还是批量?} H -->|单条| I[Create] H -->|批量| J[Create slice] B -->|修改| K{改多少字段?} K -->|1 个| L[Update col, val] K -->|部分| M{需要更新零值?} M -->|是| N[Updates map] M -->|否| O[Updates struct] K -->|全部| P[Save] B -->|删除| Q{启用 SoftDelete?} Q -->|是| R[Delete - 软删除] Q -->|否| S[Delete - 物理删除] style A fill:#4FC08D,color:#fff style E fill:#3B82F6,color:#fff style I fill:#8B5CF6,color:#fff style P fill:#EC4899,color:#fff style R fill:#F59E0B,color:#000 ``` ## 关联笔记 - [[01-安装与初始化]] - [[02-模型定义]] - [[04-条件查询]] - [[10-软删除]] - [[14-错误处理]]