全部学科
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 分库路由配置
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库