GORM 使用与性能优化 Skill
脚本工具(优先用脚本,减少 token 消耗)
使用规则:用户提供代码/SQL/参数时,先跑脚本,只输出脚本结果 + 针对性说明。
权限说明:所有脚本仅依赖 Python 3.8+,不调用任何外部 API 或凭证。 🔍 只读脚本(读 stdin 或用户指定文件,输出到 stdout,不写磁盘) 📝 写磁盘脚本(仅在用户明确传
--output参数时写文件,建议先用--dry-run预览)
| 场景 | 脚本 | 权限 | 用法示例 |
|---|---|---|---|
| 用户粘贴 Go 代码,问"有没有问题/如何优化" | scripts/analyze_gorm.py |
🔍 | python3 scripts/analyze_gorm.py - <<< "代码"(R1–R21,含 v2 专属检测) |
| 用户提供 CREATE TABLE SQL,需要生成 struct | scripts/gen_model.py |
🔍 | echo "CREATE TABLE..." | python3 scripts/gen_model.py - |
| 用户问连接池配置,提供了 QPS/实例数等参数 | scripts/pool_advisor.py |
🔍 | python3 scripts/pool_advisor.py --qps 500 --avg-latency-ms 20 --app-instances 4 |
| 用户提供 SQL,问性能/索引问题 | scripts/query_explain.py |
🔍 | python3 scripts/query_explain.py "SELECT * FROM ..." |
| 用户修改了 struct,问如何生成迁移 SQL | scripts/migration_gen.py |
🔍 | python3 scripts/migration_gen.py old.go new.go --table users |
| 用户粘贴 struct,问如何生成 Scope 函数 | scripts/scope_gen.py |
🔍 | python3 scripts/scope_gen.py model.go --tenant --paginate |
| 用户使用 PostgreSQL,需要生成 struct | scripts/gen_model.py |
🔍 | python3 scripts/gen_model.py schema.sql --dialect pg |
| 用户需要 benchmark / pprof 代码 | scripts/bench_template.py |
📝 | 默认输出到 stdout;写文件需加 --output bench_test.go |
| 新项目初始化:用户想引入 dbcore 基础包到项目 | scripts/init_project.py |
📝 | 先 --dry-run 预览,再 --output ./internal/dbcore 写入 |
核心原则
- 先测量,再优化 — 用
db.Debug()或自定义 Logger 定位慢 SQL,再做针对性优化 - 最小数据传输 — 只 Select 需要的字段,只查需要的行
- 减少 Round-trip — 批量操作、预加载 vs 懒加载权衡
- 连接复用 — 正确配置连接池,避免频繁开关连接
0. 项目初始化(脚手架)
新项目引入 dbcore 基础包(BaseModel + QueryBuilder + Transaction)时,
直接用脚本生成,无需手动复制:
# 生成到指定目录,默认 package 名 dbcore
python3 scripts/init_project.py --output ./internal/dbcore
# 自定义 package 名
python3 scripts/init_project.py --output ./pkg/db --package mydb
# 预览模式(不写入文件)
python3 scripts/init_project.py --output ./internal/dbcore --dry-run
# 强制覆盖已存在文件
python3 scripts/init_project.py --output ./internal/dbcore --force
生成的三个文件:
| 文件 | 说明 |
|---|---|
base_model.go |
泛型 BaseModel,含 CRUD / 分页 / 游标分页,已修复 Find→Take、ListAll 上限、Page 去重 |
query_builder.go |
链式查询条件构建器,已修复 InStrings/InInts args 顺序 Bug,新增 OrGroup |
transaction.go |
事务管理器,支持嵌套事务(GORM SavePoint) |
生成后需要在同包内实现两个辅助函数(根据项目 ID 策略自行实现):
// auto_id.go(需自行创建)
package dbcore
import "github.com/bwmarrin/snowflake"
var node, _ = snowflake.NewNode(1)
func autoFillID(v any) {
// 用反射检查并填充 ID 字段(string 类型,为空时生成雪花 ID)
// 具体实现见 references/base-model-pattern.md 附录
}
func autoFillIDBatch[T any](v []*T) {
for _, item := range v {
autoFillID(item)
}
}
初始化 DB 时推荐配置(详见第 1 节):
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{
SkipDefaultTransaction: true, // 写性能 +30%
PrepareStmt: true, // SQL 编译缓存
DisableForeignKeyConstraintWhenMigrating: true, // 禁止物理外键
})
1. 初始化与连接池配置
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"time"
)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
// 🚀 关闭默认事务(写操作不自动包 transaction),提升写性能约 30%
SkipDefaultTransaction: true,
// 🚀 开启 PreparedStatement 缓存,SQL 编译复用
PrepareStmt: true,
// 生产环境用 Warn 或 Error,避免日志 IO 拖慢请求
Logger: logger.Default.LogMode(logger.Warn),
// 禁止自动复数表名(按团队规范决定)
NamingConvention: schema.NamingStrategy{SingularTable: true},
})
// 连接池配置(必做)
sqlDB, _ := db.DB()
sqlDB.SetMaxOpenConns(100) // 最大连接数,根据 DB 规格设置
sqlDB.SetMaxIdleConns(20) // 空闲连接池大小,一般为 MaxOpen 的 20%
sqlDB.SetConnMaxLifetime(time.Hour) // 连接最大存活时间,防止 DB 侧主动断开
sqlDB.SetConnMaxIdleTime(10 * time.Minute)
注意:
SkipDefaultTransaction仅跳过单条 Create/Update/Delete 的隐式事务, 显式db.Transaction(func...)不受影响。
2. 查询优化
2.1 只查需要的字段
// ❌ 慢:SELECT * — 传输、序列化所有字段
db.Find(&users)
// ✅ 快:SELECT id, name, email
db.Select("id", "name", "email").Find(&users)
// ✅ 更好:投影到小 struct,减少内存分配
type UserDTO struct {
ID uint
Name string
Email string
}
db.Model(&User{}).Select("id", "name", "email").Find(&dtos)
2.2 避免 N+1 查询
// ❌ N+1:先查 users,再对每个 user 查 orders(N 次额外查询)
db.Find(&users)
for _, u := range users {
db.Where("user_id = ?", u.ID).Find(&u.Orders)
}
// ✅ Preload:两次查询解决,适合关联数据量不大的场景
db.Preload("Orders").Find(&users)
// ✅ Joins:一次 JOIN 查询,适合需要过滤关联字段的场景
db.Joins("JOIN orders ON orders.user_id = users.id AND orders.status = ?", "paid").
Find(&users)
// ✅ 批量 Preload 并加条件
db.Preload("Orders", "status = ?", "paid").
Preload("Orders.Items").
Find(&users)
Preload vs Joins 选择:
- 需要过滤主表 by 关联字段 →
Joins- 只是附带加载关联数据 →
Preload- 关联数据量大(万级以上)→ 考虑手动分页查询
2.3 分批处理大数据集
// ❌ 危险:一次性加载百万行进内存
db.Find(&allUsers)
// ✅ FindInBatches:每批 500 条
db.Model(&User{}).Where("status = ?", "active").
FindInBatches(&batch, 500, func(tx *gorm.DB, batchNum int) error {
for i := range batch {
process(&batch[i])
}
return nil // 返回 error 则中止
})
// ✅ 游标分页(大表推荐,避免 OFFSET 性能退化)
var lastID uint
for {
var users []User
db.Where("id > ?", lastID).Order("id").Limit(500).Find(&users)
if len(users) == 0 { break }
for _, u := range users { process(u) }
lastID = users[len(users)-1].ID
}
2.4 使用索引提示
import "gorm.io/hints"
// 强制使用指定索引
db.Clauses(hints.UseIndex("idx_user_name")).Find(&users)
db.Clauses(hints.ForceIndex("idx_created_at").ForOrderBy()).Find(&orders)
3. 写操作优化
3.1 批量插入
// ❌ 慢:逐条 INSERT,N 次 Round-trip
for _, u := range users {
db.Create(&u)
}
// ✅ 批量 INSERT,单次 Round-trip
db.Create(&users) // GORM 自动批量(默认无分批)
// ✅ 指定批次大小,防止单条 SQL 过大
db.CreateInBatches(&users, 200)
3.2 按需更新,不更新零值
// ❌ Updates with struct 会跳过零值字段,可能漏更新
db.Model(&user).Updates(User{Name: "new", Age: 0}) // Age=0 被忽略!
// ✅ 用 map 明确指定需要更新的字段
db.Model(&user).Updates(map[string]any{
"name": "new",
"age": 0,
})
// ✅ 精确更新单字段
db.Model(&user).Update("name", "new")
// ✅ Select 限制只更新指定字段(防止意外覆盖)
db.Model(&user).Select("name", "email").Updates(&user)
// ✅ Select("*") 强制更新所有字段(包括零值),v2 新增
db.Model(&user).Select("*").Updates(&user)
// ✅ Omit 排除特定字段不更新
db.Model(&user).Omit("password", "created_at").Updates(&user)
3.3 高效 Upsert
// MySQL
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "email"}},
DoUpdates: clause.AssignmentColumns([]string{"name", "updated_at"}),
}).Create(&user)
// 忽略冲突
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&users)
4. 事务管理
// ✅ 标准事务:自动 rollback on error
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&order).Error; err != nil {
return err // 触发 rollback
}
if err := tx.Model(&stock).Update("qty", gorm.Expr("qty - ?", 1)).Error; err != nil {
return err
}
return nil // 触发 commit
})
// ✅ 手动事务(跨函数传递 tx)
tx := db.Begin()
defer func() {
if r := recover(); r != nil { tx.Rollback() }
}()
// ... 使用 tx 操作 ...
tx.Commit()
// ⚠️ 大批量写不需要事务一致性时,使用 Session 临时禁用
db.Session(&gorm.Session{SkipDefaultTransaction: true}).CreateInBatches(&records, 500)
5. 读写分离
import "gorm.io/plugin/dbresolver"
db.Use(dbresolver.Register(dbresolver.Config{
Sources: []gorm.Dialector{mysql.Open(writeDSN)},
Replicas: []gorm.Dialector{mysql.Open(read1DSN), mysql.Open(read2DSN)},
Policy: dbresolver.RandomPolicy{},
// 连接池可以对 source/replica 分开配置
}).SetMaxOpenConns(50).SetMaxIdleConns(10))
// GORM 自动路由:Find/First → Replica,Create/Update/Delete → Source
// 强制走主库(刚写完立即读的场景):
db.Clauses(dbresolver.Write).Find(&user)
6. Scopes 与多租户
6.1 可复用查询条件(Scopes)
// 定义 Scope:签名固定为 func(*gorm.DB) *gorm.DB
func ActiveUser(db *gorm.DB) *gorm.DB {
return db.Where("status = ?", "active")
}
func AgeOver(age int) func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("age > ?", age)
}
}
// 组合使用
db.Scopes(ActiveUser, AgeOver(18)).Order("created_at DESC").Find(&users)
自动生成 Scope:
python3 scripts/scope_gen.py model.go --tenant --paginate
6.2 多租户行级隔离
// 从 context 提取 tenant_id,注入所有查询
func TenantScope(ctx context.Context) func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
tenantID, ok := ctx.Value("tenant_id").(uint)
if !ok || tenantID == 0 {
return db.Where("1 = 0") // 无租户信息时拒绝访问
}
return db.Where("tenant_id = ?", tenantID)
}
}
// Service 层统一使用
db.WithContext(ctx).Scopes(TenantScope(ctx)).Find(&orders)
完整示例(分页 Scope、软删除 Scope、版本号 Scope)见
references/scopes.md
7. Model 设计规范
7.1 基础 Model
// 嵌入 gorm.Model 获取 ID/CreatedAt/UpdatedAt/DeletedAt(软删除)
type Order struct {
gorm.Model
UserID uint `gorm:"not null;index"` // 逻辑外键:只加索引,不加 CONSTRAINT
Status string `gorm:"type:varchar(20);index"` // 状态字段加索引
Amount int64 `gorm:"not null;default:0"`
}
// 复合索引
type Log struct {
gorm.Model
UserID uint `gorm:"index:idx_user_time"` // 复合索引
CreatedAt time.Time `gorm:"index:idx_user_time"` // 复合索引
}
// 软删除:DeletedAt 字段存在时,Delete 只设置时间戳
// 查询自动带上 WHERE deleted_at IS NULL
// 彻底删除:db.Unscoped().Delete(&user)
7.2 ❌ 禁止使用物理外键(Foreign Key Constraint)
团队规范:禁止在数据库层面创建
FOREIGN KEY CONSTRAINT,一律采用逻辑外键(应用层约束)。
物理外键的问题:
| 问题 | 说明 |
|---|---|
| 分库分表不兼容 | 跨分片的 FK 约束无法建立,迁移到 Sharding 时必须删除 |
| 级联操作不可控 | ON DELETE CASCADE 可能引发意外批量删除,难以追踪 |
| 性能开销 | 每次写操作都触发 FK 校验,高并发场景下成为瓶颈 |
| 导入/迁移困难 | 大批量数据导入必须严格按依赖顺序执行,运维复杂 |
| 微服务不友好 | 跨服务数据无法建立 DB 级别约束 |
GORM 中禁止物理外键的写法:
// ❌ 错误:会在 AutoMigrate 时创建 FOREIGN KEY CONSTRAINT
type Order struct {
gorm.Model
UserID uint
User User `gorm:"foreignKey:UserID"` // 会触发建 FK
}
// ✅ 正确:只建索引,不建 FK 约束
type Order struct {
gorm.Model
UserID uint `gorm:"not null;index"` // 逻辑外键,仅索引
// 不声明 User 关联字段,或使用 constraint:false 禁止建约束
}
// ✅ 如果必须保留关联查询,显式禁止创建约束
type Order struct {
gorm.Model
UserID uint `gorm:"not null;index"`
User User `gorm:"foreignKey:UserID;constraint:false"` // 禁止建 FK
}
AutoMigrate 全局禁止 FK:
// 如果使用 AutoMigrate,禁用所有 FK 创建
db.DisableForeignKeyConstraintWhenMigrating = true
// 或在 gorm.Config 中配置
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true, // 全局禁止 FK 约束
})
应用层约束替代方案:
// 写入时在 Service 层校验引用完整性
func (s *OrderService) CreateOrder(ctx context.Context, order *Order) error {
// 应用层校验 user 存在
var count int64
if err := s.db.WithContext(ctx).Model(&User{}).
Where("id = ?", order.UserID).Count(&count).Error; err != nil {
return err
}
if count == 0 {
return ErrUserNotFound
}
return s.db.WithContext(ctx).Create(order).Error
}
8. 调试与性能分析
// 单次查询打印 SQL
db.Debug().Find(&users)
// 全局 Logger(开发环境)
db, _ = gorm.Open(dsn, &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
// 自定义慢查询 Logger(生产推荐)
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: 200 * time.Millisecond, // 超过 200ms 打印
LogLevel: logger.Warn,
IgnoreRecordNotFoundError: true,
Colorful: false,
},
)
// DryRun 模式:只生成 SQL,不执行(用于调试复杂查询)
stmt := db.Session(&gorm.Session{DryRun: true}).Find(&users).Statement
fmt.Println(stmt.SQL.String()) // 打印最终 SQL
fmt.Println(stmt.Vars) // 打印参数
9. 常见坑与反模式
| 问题 | 错误写法 | 正确做法 |
|---|---|---|
| 忘记传 Context | db.Find(&u) |
db.WithContext(ctx).Find(&u) |
| struct Updates 丢零值 | db.Updates(User{Age:0}) |
db.Updates(map[string]any{"age":0}) |
| 大 OFFSET 分页 | db.Offset(100000).Limit(20) |
游标分页(Where id > lastID) |
| 未用索引的 Like | WHERE name LIKE '%foo%' |
前缀匹配 LIKE 'foo%' 或全文索引 |
| 事务内做耗时操作 | 事务 + HTTP 调用 | 事务只包 DB 操作,HTTP 调用放事务外 |
| 未检查 Error | db.Find(&u); use u |
if err := db.Find(&u).Error; err != nil |
| 使用物理外键 | gorm:"foreignKey:UserID" 未加 constraint:false |
禁止 DB 级 FK,改用应用层校验 + constraint:false |
10. 缓存集成(Cache-Aside)
详细示例见
references/caching.md,以下为核心模式。
func (r *UserRepo) GetUser(ctx context.Context, id uint) (*User, error) {
key := fmt.Sprintf("user:%d", id)
// 1. 读 Redis
if val, err := r.rdb.Get(ctx, key).Bytes(); err == nil {
var u User
json.Unmarshal(val, &u)
return &u, nil
}
// 2. 查 DB(singleflight 防击穿)
res, err, _ := r.group.Do(key, func() (any, error) {
var u User
if err := r.db.WithContext(ctx).First(&u, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
r.rdb.Set(ctx, key, "null", time.Minute) // 防穿透:缓存空值
return nil, ErrNotFound
}
return nil, err
}
data, _ := json.Marshal(u)
// TTL 加随机抖动,防雪崩
ttl := 30*time.Minute + time.Duration(rand.Int63n(int64(6*time.Minute)))
r.rdb.Set(ctx, key, data, ttl)
return &u, nil
})
if err != nil { return nil, err }
return res.(*User), nil
}
// 写操作:更新 DB → 删缓存(不更新缓存,避免并发不一致)
func (r *UserRepo) UpdateUser(ctx context.Context, u *User) error {
if err := r.db.WithContext(ctx).Save(u).Error; err != nil { return err }
r.rdb.Del(ctx, fmt.Sprintf("user:%d", u.ID))
return nil
}
布隆过滤器防穿透、延迟双删、列表缓存版本号方案详见
references/caching.md
11. 分库分表(Sharding)
详细配置见
references/sharding.md,以下为快速索引。
import "gorm.io/sharding"
db.Use(sharding.Register(sharding.Config{
ShardingKey: "user_id",
NumberOfShards: 64,
PrimaryKeyGenerator: sharding.PKSnowflake,
}, "orders")) // 对 orders 表分片
// 查询自动路由到对应分片
db.Where("user_id = ?", 100).Find(&orders)
// 广播查询(跨分片,慎用)
db.Where("status = ?", "pending").Find(&orders)
分片键选择原则:
- 高基数字段(user_id / tenant_id),避免数据倾斜
- 查询条件中出现频率最高的字段
- 避免跨分片 JOIN 和聚合
双写迁移、自定义分片算法、跨分片查询详见
references/sharding.md
12. 监控与可观测性
详细配置见
references/observability.md,以下为快速索引。
12.1 Prometheus 指标
import "github.com/go-gorm/prometheus"
db.Use(prometheus.New(prometheus.Config{
DBName: "myapp",
RefreshInterval: 15, // 每 15s 刷新指标
MetricsCollector: []prometheus.MetricsCollector{
&prometheus.MySQL{VariableNames: []string{"Threads_running"}},
},
}))
// 自动暴露: gorm_dbstats_max_open_connections, idle_connections, in_use 等
12.2 慢查询回调
db.Callback().Query().After("gorm:query").Register("slowlog", func(db *gorm.DB) {
if db.Statement.SQL.String() != "" {
elapsed := db.Statement.DB.(*gorm.DB).Statement.BuildCondition
// 超过阈值则告警
}
})
// 更简洁:直接用自定义 Logger
newLogger := logger.New(writer, logger.Config{
SlowThreshold: 200 * time.Millisecond,
LogLevel: logger.Warn,
})
12.3 OpenTelemetry 链路追踪
import "github.com/uptrace/opentelemetry-go-extra/otelgorm"
if err := db.Use(otelgorm.NewPlugin(
otelgorm.WithDBName("myapp"),
)); err != nil {
panic(err)
}
// 每次 DB 操作自动产生 span,关联到父 trace
db.WithContext(ctx).Find(&users) // ctx 需携带 trace context
Grafana 仪表盘配置、连接池健康检查、告警规则详见
references/observability.md
13. GORM v2 核心机制
14.1 Session 与 goroutine 安全
// ❌ 危险:链式条件累积,跨 goroutine 数据竞争
base := db.Where("tenant_id = ?", tenantID)
go func() { base.Find(&list1) }() // 数据竞争!
go func() { base.Find(&list2) }() // 数据竞争!
// ✅ 每次查询 Session 隔离
base := db.Where("tenant_id = ?", tenantID)
go func() { base.Session(&gorm.Session{NewDB: true}).Find(&list1) }()
go func() { base.Session(&gorm.Session{NewDB: true}).Find(&list2) }()
// ✅ ToSQL 调试(不执行)
sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
return tx.Where("id > ?", 100).Limit(10).Find(&users)
})
完整 Session 配置项、PrepareStmt 缓存清理、陷阱汇总见
references/session.md
14.2 Clause 系统
// FOR UPDATE(库存/余额扣减)
db.Clauses(clause.Locking{Strength: "UPDATE"}).First(&stock)
// SKIP LOCKED(任务队列抢占)
db.Clauses(clause.Locking{Strength: "UPDATE", Options: "SKIP LOCKED"}).
Limit(10).Find(&tasks)
// Upsert(OnConflict)
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "email"}},
DoUpdates: clause.AssignmentColumns([]string{"name", "updated_at"}),
}).Create(&user)
// RETURNING(PostgreSQL)
db.Clauses(clause.Returning{}).Create(&user) // 自动填充 DB 生成的字段
Clause 完整用法、自定义 Clause 表达式见
references/clause.md
14.3 Association 关联操作
// 禁止物理外键,关联字段加 constraint:false
type User struct {
Orders []Order `gorm:"foreignKey:UserID;constraint:false"`
}
// Preload(推荐:两次查询,内存友好)
db.Preload("Orders", "status = ?", "paid").Find(&users)
db.Preload(clause.Associations).Find(&users) // 预加载所有关联
// 精确控制级联写入
db.Omit(clause.Associations).Create(&user) // 只写 user,跳过所有关联
db.Select("Orders").Create(&user) // 只写 user + Orders
// Association 操作
db.Model(&user).Association("Orders").Append(&order) // 添加关联
db.Model(&user).Association("Orders").Replace(&order) // 替换所有关联
db.Model(&user).Association("Orders").Delete(&order) // 移除关联
完整示例、多对多、软删除处理见
references/association.md
14.4 Serializer 与自定义类型
// JSON 序列化(推荐替代 string 存 JSON)
type User struct {
Tags []string `gorm:"type:json;serializer:json"`
Address Address `gorm:"type:json;serializer:json"`
}
// 自定义类型(枚举 / Money / 加密字段)
type OrderStatus int8
func (s OrderStatus) Value() (driver.Value, error) { return int64(s), nil }
func (s *OrderStatus) Scan(v interface{}) error { *s = OrderStatus(v.(int64)); return nil }
// 自定义加密 Serializer
type User struct {
Phone string `gorm:"serializer:encrypted"` // 存储时自动加密,读取时自动解密
}
完整实现(Money 类型、GormDataType 接口、选型建议)见
references/serializer.md
14.5 Error 处理规范(v2)
// ✅ v2 正确写法
if errors.Is(err, gorm.ErrRecordNotFound) { }
// ❌ v1 旧写法,v2 已移除(analyze_gorm.py R21 会检测)
if gorm.IsRecordNotFoundError(err) { }
// Find 不触发 ErrRecordNotFound(返回空切片)
// First / Take / Last 触发 ErrRecordNotFound
14. 进阶参考
详细专题见 references/ 目录(按需加载,不要全量读入):
| 文件 | 内容 | 触发时机 |
|---|---|---|
references/hooks.md |
BeforeCreate/AfterUpdate 等 Hooks | 用户问 Hook 使用或性能 |
references/raw-sql.md |
Raw SQL / Scan / Rows | 用户需要绕开 ORM 写原生 SQL |
references/indexing.md |
GORM Tag 定义索引、复合索引 | 用户问索引如何在 struct 上定义 |
references/concurrency.md |
乐观锁、悲观锁、CAS 原子更新 | 用户问并发冲突、超卖、转账等场景 |
references/testing.md |
sqlmock 单测、SQLite 集成测试、事务回滚隔离 | 用户问 GORM 代码怎么写单测 |
references/migration.md |
golang-migrate 规范、大表在线 DDL | 用户问数据库迁移、AutoMigrate 的生产使用 |
references/sharding.md |
分库分表配置、分片算法、双写迁移 | 用户问分库分表、水平拆分 |
references/observability.md |
Prometheus、OpenTelemetry、慢查询告警 | 用户问监控、可观测性、链路追踪 |
references/scopes.md |
可复用 Scope、分页、多租户行级隔离 | 用户问 Scope 用法、多租户设计 |
references/caching.md |
Cache-Aside、防击穿/雪崩/穿透、缓存一致性 | 用户问 GORM + Redis 缓存集成 |
references/base-model-pattern.md |
泛型 BaseModel 常见 Bug、游标分页、多租户强制隔离 | 用户问 BaseModel 设计、QueryBuilder、分页优化 |
references/session.md |
Session 机制、goroutine 安全、条件累积防范 | 用户问 Session、db 复用、DryRun |
references/clause.md |
Clause 系统(Upsert/FOR UPDATE/RETURNING/自定义) | 用户问 FOR UPDATE、Upsert、RETURNING |
references/association.md |
Association 关联操作、Preload/Joins、级联控制 | 用户问关联加载、Preload、多对多 |
references/serializer.md |
Serializer、自定义数据类型(枚举/Money/加密) | 用户问字段序列化、自定义类型映射 |
