Skip to content
Go
高级特性

高级特性

本章覆盖 GORM 的进阶功能:连接池调优、Logger 定制、DryRun 调试、Context 集成、Serializer、性能优化技巧等。


连接池深入配置

MySQL 连接池

import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

sqlDB, _ := db.DB()

// 基础配置
sqlDB.SetMaxOpenConns(100)               // 最大打开连接
sqlDB.SetMaxIdleConns(10)                // 最大空闲连接
sqlDB.SetConnMaxLifetime(30 * time.Minute) // 连接最大存活时间
sqlDB.SetConnMaxIdleTime(5 * time.Minute)  // 空闲连接最大存活时间

// 查看连接池状态
stats := sqlDB.Stats()
log.Printf("Pool: open=%d idle=%d inUse=%d waiting=%d maxIdleClosed=%d maxLifetimeClosed=%d",
    stats.OpenConnections,  // 当前打开的连接数
    stats.Idle,             // 空闲连接数
    stats.InUse,            // 正在使用的连接数
    stats.WaitCount,        // 累计等待次数
    stats.MaxIdleClosed,    // 因超过空闲时间关闭的连接数
    stats.MaxLifetimeClosed,// 因超过生命周期关闭的连接数
)

PostgreSQL 连接池

// PostgreSQL 特有配置
dsn := "host=localhost port=5432 user=postgres password=pass dbname=mydb " +
       "sslmode=disable " +
       "connect_timeout=10 " +       // 连接超时
       "pool_max_conns=100 " +       // 连接池最大连接(pgx 驱动)
       "pool_min_conns=10 " +        // 最小连接数
       "pool_max_conn_lifetime=1h " + // 连接最大存活
       "pool_max_conn_idle_time=30m " // 空闲连接存活
       "pool_health_check_period=1m"   // 健康检查间隔

性能提示:PostgreSQL 每个连接都是一个独立进程(fork),维护成本高于 MySQL 的线程模型。PG 的 MaxIdleConns 不宜设太大(10 左右即可),MySQL 可设到 25~50。


TLS / SSL 连接

// MySQL TLS
dsn := "user:pass@tcp(host:3306)/db?tls=custom&charset=utf8mb4&parseTime=True"

// 注册自定义 TLS 配置
rootCertPool := x509.NewCertPool()
pem, _ := os.ReadFile("/path/to/ca-cert.pem")
rootCertPool.AppendCertsFromPEM(pem)
mysql.RegisterTLSConfig("custom", &tls.Config{
    RootCAs: rootCertPool,
})

// PostgreSQL SSL
dsn := "host=host user=user password=pass dbname=db " +
       "sslmode=verify-full " +
       "sslrootcert=/path/to/ca.pem " +
       "sslcert=/path/to/client-cert.pem " +
       "sslkey=/path/to/client-key.pem"

Logger — SQL 日志定制

内置 Logger 级别

import (
    "gorm.io/gorm/logger"
    "log"
    "os"
    "time"
)

newLogger := logger.New(
    log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
    logger.Config{
        SlowThreshold:             200 * time.Millisecond, // 慢查询阈值
        LogLevel:                  logger.Info,            // 日志级别
        IgnoreRecordNotFoundError: true,                   // 忽略 RecordNotFound 错误
        ParameterizedQueries:      false,                  // 参数化查询(用 ? 替代参数值)
        Colorful:                  true,                   // 彩色输出
    },
)

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    Logger: newLogger,
})

日志级别

Level 说明
Silent 1 不输出任何日志
Error 2 仅输出错误 SQL
Warn 3 输出错误 + 慢查询 SQL
Info 4 输出所有 SQL
// 开发环境
db.Logger = db.Logger.LogMode(logger.Info)

// 生产环境
db.Logger = db.Logger.LogMode(logger.Warn)

// 单次会话覆盖
db.Session(&gorm.Session{Logger: logger.Default.LogMode(logger.Info)}).Find(&users)

慢查询日志

newLogger := logger.New(
    log.New(os.Stdout, "\r\n", log.LstdFlags),
    logger.Config{
        SlowThreshold: 200 * time.Millisecond, // 超过此阈值会以 WARN 级别输出
        LogLevel:      logger.Warn,
    },
)

// 输出示例:
// [WARN] SLOW SQL >= 200ms [210.456ms] [rows:1] SELECT * FROM users WHERE name = '张三'

DryRun — 只生成 SQL 不执行

// 会话级 DryRun
stmt := db.Session(&gorm.Session{DryRun: true}).First(&user, 1).Statement
sql := stmt.SQL.String() // "SELECT * FROM users WHERE id = ?"
vars := stmt.Vars          // [1]
fmt.Println(db.Dialector.Explain(sql, vars...))

// 全局 DryRun
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{DryRun: true})

💡 DryRun 非常适用于调试——在不修改任何数据的前提下,检查 GORM 实际生成的 SQL 是什么。


Context 集成 — 超时与取消

// 请求级超时控制
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

var users []User
err := db.WithContext(ctx).Where("age > ?", 18).Find(&users).Error
if errors.Is(err, context.DeadlineExceeded) {
    log.Println("查询超时")
}

// 请求取消(如 HTTP 客户端断开连接)
func handler(c *gin.Context) {
    // Gin 的 c.Request.Context() 会在客户端断开时取消
    db.WithContext(c.Request.Context()).Find(&users)
}

💡 最佳实践:所有与用户请求相关的 DB 操作都应使用 db.WithContext(ctx) 传递请求 Context,确保超时或客户端断开时能及时释放数据库连接。


Serializer — 字段序列化器

GORM v2 内置了序列化器,用于自动在 Go 类型和数据库存储格式之间转换:

type User struct {
    gorm.Model
    Name     string
    Settings  map[string]interface{} `gorm:"serializer:json"`    // JSON → MySQL JSON / PG JSONB
    Tags      []string               `gorm:"serializer:json"`    // 数组 → JSON
    Birthday  int64                  `gorm:"serializer:unixtime"` // Unix 时间戳
    Metadata  map[string]string      `gorm:"serializer:gob"`     // Gob 编码
}

user := User{
    Name:    "张三",
    Settings: map[string]interface{}{"theme": "dark", "lang": "zh"},
    Tags:    []string{"vip", "verified"},
}
db.Create(&user)
// Settings → {"theme": "dark", "lang": "zh"}
// Tags → ["vip", "verified"]

自定义序列化器

type EncryptedString string

type EncryptedStringSerializer struct{}

func (s *EncryptedStringSerializer) Scan(ctx context.Context, field *schema.Field, dst reflect.Value, dbValue interface{}) error {
    // 从数据库读取时解密
    if dbValue == nil { return nil }
    decrypted := aesDecrypt(dbValue.(string))
    field.Set(ctx, dst, decrypted)
    return nil
}

func (s *EncryptedStringSerializer) Value(ctx context.Context, field *schema.Field, dst reflect.Value, fieldValue interface{}) (interface{}, error) {
    // 写入数据库时加密
    return aesEncrypt(fieldValue.(string)), nil
}

// 注册
schema.RegisterSerializer("encrypted", &EncryptedStringSerializer{})

// 使用
type User struct {
    gorm.Model
    Phone EncryptedString `gorm:"serializer:encrypted"`
}

命名参数与 Raw SQL

// 命名参数
db.Where("name = @name AND age > @age", sql.Named("name", "张三"), sql.Named("age", 18))

// Raw + 命名参数
db.Raw("SELECT * FROM users WHERE name = @name", sql.Named("name", "张三"))

// 复杂原生 SQL
type OrderStats struct {
    UserID uint
    Total  float64
    Count  int
}
var stats []OrderStats
db.Raw(`
    SELECT user_id, SUM(amount) as total, COUNT(*) as count
    FROM orders
    WHERE created_at >= @start AND created_at < @end
    GROUP BY user_id
    HAVING total > @min_total
`, sql.Named("start", startDate),
   sql.Named("end", endDate),
   sql.Named("min_total", 1000),
).Scan(&stats)

性能优化清单

1. 关闭默认事务

// 大量单条写入时,关闭默认事务可提升约 30% 性能
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{
    SkipDefaultTransaction: true,
})

2. 使用预编译语句

db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{
    PrepareStmt: true, // 缓存预编译语句
})

⚡ PrepareStmt 将 SQL 预编译后缓存,避免重复编译,提升 10%~20% 的重复查询性能。

3. 批量插入

// ❌ 慢:逐条插入
for _, user := range users {
    db.Create(&user)
}

// ✅ 快:批量插入
db.CreateInBatches(users, 100)

4. 选择性查询字段

// ❌ SELECT *(字段多时会浪费内存和带宽)
db.Find(&users)

// ✅ 只选需要的字段
db.Select("id", "name").Find(&users)

5. 正确使用索引

// ❌ 函数包裹索引列 → 不走索引
db.Where("DATE(created_at) = ?", "2025-01-01")

// ✅ 直接范围查询 → 走索引
db.Where("created_at >= ? AND created_at < ?", "2025-01-01", "2025-01-02")

6. 避免 N+1 查询

// ❌ N+1 查询
var users []User
db.Find(&users)
for i, u := range users {
    db.Where("user_id = ?", u.ID).Find(&users[i].Orders)
}

// ✅ 使用 Preload
db.Preload("Orders").Find(&users)

7. 连接池复用

// 全局单例 db,不要每次请求重新 gorm.Open
var DB *gorm.DB

func init() {
    var err error
    DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal(err)
    }
}

8. 读写分离

// 使用 GORM 插件实现读写分离
import "gorm.io/plugin/dbresolver"

db.Use(dbresolver.Register(dbresolver.Config{
    Sources:  []gorm.Dialector{mysql.Open(masterDSN)},   // 写库
    Replicas: []gorm.Dialector{
        mysql.Open(slave1DSN),                            // 读库 1
        mysql.Open(slave2DSN),                            // 读库 2
    },
    Policy: dbresolver.RandomPolicy{}, // 随机负载均衡
}))

// 自动路由:写 → Sources,读 → Replicas
db.Clauses(dbresolver.Write).Create(&user) // 强制走写库
db.Clauses(dbresolver.Read).Find(&users)    // 强制走读库

常见陷阱汇总

陷阱 说明
🚨 频繁 Open 每次请求 gorm.Open 是极端的资源浪费,应该用全局单例
🚨 PrepareStmt 内存泄漏 启用 PrepareStmt 后,每个唯一的 SQL 都会缓存(内部用 map),长时间运行需关注内存
🚨 Context 不传递 不传 Context 导致请求级别的超时/取消无法生效
🚨 LogLevel=Info 生产 生产环境用 Info 级别输出每条 SQL 会拖慢性能并填满日志
🚨 慢查询盲区 未设置 SlowThreshold 导致慢查询无法及时发现
🚨 MaxOpenConns=0 默认无限制,生产环境必须限制