全部学科
Python全栈
python
NodeJS全栈
nodejs
📅 2026-05-21 8 分钟 ✍️ juanwangdev

预编译语句原理

预编译语句(Prepared Statement)可提升重复查询性能,本文解析 GORM 的预编译机制。

预编译基础

工作原理

Go
普通执行:  SQL拼接 → 解析 → 编译 → 执行 → 返回
预编译执行: 编译一次 → 缓存 → 多次执行(仅传参数)

Go 底层支持

Go
// database/sql 提供预编译支持
stmt, err := db.Prepare("SELECT * FROM users WHERE id = ?")
defer stmt.Close()

// 执行时仅传参数
row := stmt.QueryRow(1)

GORM PrepareStmt 模式

启用方式

Go
// 方式1:全局启用
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    PrepareStmt: true,
})

// 方式2:会话级启用
tx := db.Session(&gorm.Session{PrepareStmt: true})

内部实现

Go
type StmtStore struct {
    sync.RWMutex
    queries map[string]Stmt
}

type Stmt struct {
    *sql.Stmt
    Score int // 访问计数,用于 LRU 淘汰
}

缓存流程:

Go
func (c *PreparedStmtDB) GetStmtConn(db *sql.DB, sql string) (*sql.Stmt, error) {
    c.Lock()
    defer c.Unlock()
    
    if stmt, ok := c.queries[sql]; ok {
        stmt.Score++ // 命中缓存,增加热度
        return stmt.Stmt, nil
    }
    
    // 未命中,预编译新语句
    s, err := db.Prepare(sql)
    c.queries[sql] = Stmt{Stmt: s, Score: 1}
    return s, err
}

性能分析

适用场景

场景普通模式PrepareStmt说明
单次查询编译开销大于执行
同构批量查询一般编译一次,多次执行
高并发短查询一般避免重复编译,降低 CPU
动态 SQL不适用每次 SQL 不同,缓存失效

基准测试参考

Go
// 场景:循环执行相同查询 1000 次
// 普通模式: ~120ms
// PrepareStmt: ~45ms (提升约 62%)

缓存管理

连接池交互

Go
// 预编译语句与连接绑定
// GORM 内部维护连接级别的缓存
type ConnPool struct {
    Pool      ConnPool
    StmtCache map[uint]*PreparedStmtDB // 每个连接独立缓存
}

清理策略

text
// 手动清理
db.Callback().Raw().Replace("gorm:raw", func(tx *gorm.DB) {
    // 清除当前会话所有预编译缓存
    tx.ConnPool.(*PreparedStmtDB).Reset()
})

注意事项

PrepareStmt 会增加连接内存占用,低频查询场景不建议启用。

动态条件查询(如不同字段组合 WHERE)会导致缓存命中率低,反而浪费内存。

长连接下缓存累积需定期清理,避免 OOM。

事务内预编译语句与事务连接绑定,事务结束自动释放。

要点总结

  • 预编译语句一次编译多次执行,适合高频同构查询
  • GORM 通过 PrepareStmt: true 启用内置缓存机制
  • 内部使用 StmtStore 按 SQL 字符串缓存,LRU 策略管理
  • 动态 SQL 场景不适用,缓存命中率低
  • 需关注内存占用,必要时手动清理缓存

存放路径:D:\git2\jwdev\articles\GORM\专家\源码分析与底层原理\预编译语句原理.md

想在手机上练习这篇文章的配套题目?
使用微信卷王开发者小程序,打开首页顶部扫码功能识别二维码
← 上一篇 反射与元编程应用
下一篇 → GORM 分表策略实现
扫码体验小程序
加载中
想在手机上刷题学习?
使用微信卷王开发者小程序,打开首页顶部扫码功能识别二维码