session是克隆db实例并复用连接池,不新建连接,无法隔离事务或切换数据库,仅覆盖局部配置。

Session 不是“新开一个连接”,而是克隆当前 DB 实例并注入局部配置,所有操作仍走同一连接池;误以为 Session 能隔离事务或连接,是常见误解。
Session 本质是克隆 + 配置覆盖,不是新连接
每次调用 db.Session(&gorm.Session{...}),GORM 都会调用 getInstance() 克隆出一个新 *gorm.DB,但底层 ConnPool(即 *sql.DB)完全复用。这意味着:
-
Session无法切换数据库、驱动或连接池参数(如SetMaxOpenConns) - 事务控制仍依赖
Begin()/Commit(),Session本身不开启事务 -
clone > 1的实例共享同一上下文和语句缓存,比如PrepareStmt缓存对所有同源 Session 可见 - 若在 Session 中调用
Transaction(),它只作用于该 Session 实例,不影响原始db
DryRun 模式下 SQL 生成与变量绑定必须配对使用
DryRun: true 仅阻止执行,不改变 SQL 构建逻辑。但直接取 stmt.SQL.String() 得到的是带占位符的语句(如 WHERE id = ?),不能直接运行——你得用 db.Dialector.Explain() 替换变量:
sess := db.Session(&gorm.Session{DryRun: true})
stmt := sess.First(&user, 1).Statement
sql := stmt.SQL.String() // "SELECT * FROM `users` WHERE `id` = ?"
vars := stmt.Vars // []interface{}{1}
finalSQL := db.Dialector.Explain(sql, vars) // "SELECT * FROM `users` WHERE `id` = 1"
注意:Explain 是调试用途,生成的 SQL 不保证安全,不可用于生产拼接。
- PostgreSQL 驱动输出
$1占位符,MySQL 输出?,别硬编码匹配 -
vars是按顺序填充的,若含nil指针字段,对应位置仍是nil,Explain不做空值转换 - 嵌套结构体或关联查询时,
vars可能包含多个层级值,顺序取决于 GORM 解析 Clause 的内部顺序
PrepareStmt 开启后缓存归属 Session 还是连接池?
PrepareStmt: true 在 Session 级启用时,预编译语句实际注册到连接池级的 *PreparedStmtDB,不是 Session 局部。这意味着:
- 同一个
*sql.DB下的所有 Session 共享同一份预编译缓存 -
sess.Session(&gorm.Session{PrepareStmt: true})后调用sess.First(),后续任意同源 Session 执行相同 SQL 都命中缓存 - 关闭缓存需调用
stmtManger.Close(),但它关的是整个连接池的缓存,不是单个 Session - 高频动态 SQL(如带不同 LIMIT/OFFSET 的分页)开启
PrepareStmt反而增加内存开销,缓存键是完整 SQL 字符串
CreateBatchSize 和 SkipHooks 等配置的实际生效边界
像 CreateBatchSize、SkipHooks、FullSaveAssociations 这类配置,只影响调用链中「紧邻的下一个操作」,不会透传给子操作:
-
db.Session(&gorm.Session{SkipHooks: true}).Create(&u)→ 跳过BeforeCreate/AfterCreate,但若u有HasMany关联且FullSaveAssociations: false(默认),关联数据仍会触发自己的钩子 -
db.Session(&gorm.Session{CreateBatchSize: 10}).Create(&users)→ 仅对本次Create分批,Update或Delete不受此影响 -
SkipDefaultTransaction: true必须在全局gorm.Config设置才生效,Session 级设置无效
真正容易被忽略的是:Session 配置不具备继承性。比如你用 tx := db.Begin() 得到事务实例,再 tx.Session(...),配置只作用于该 Session,不会让整个事务块都跳钩子或开 DryRun。











