在 go 的 database/sql 中,直接在事务(tx)外使用普通 db 连接执行查询,再与事务内操作交叉访问同一数据时,极易引发 postgresql 会话级死锁;根本原因在于事务和非事务操作分属不同数据库会话,破坏了隔离一致性。
在 go 的 database/sql 中,直接在事务(tx)外使用普通 db 连接执行查询,再与事务内操作交叉访问同一数据时,极易引发 postgresql 会话级死锁;根本原因在于事务和非事务操作分属不同数据库会话,破坏了隔离一致性。
Go 的 database/sql 包中,事务(*sql.Tx)和普通数据库句柄(*sql.DB)是完全独立的会话上下文。正如问题示例所示:
tx, _ := connection.Begin() // 启动新事务会话(Session A) tx.Exec(insert_statement) // 在 Session A 中插入数据(未提交) rows, _ := connection.Query(select_statement) // ❌ 在另一个会话(Session B)中查询——可能阻塞! rows.Close() tx.Exec(insert_statement_2) // 若上一步卡住,则永远无法执行 tx.Commit()
此处 connection.Query(...) 使用的是 *sql.DB 的连接池中的全新连接,它与 tx 所在的 PostgreSQL 后端进程(backend process)互不相关。若 select_statement 涉及被 insert_statement 修改但尚未提交的行(例如 SELECT ... FOR UPDATE、或触发行级锁的关联查询),PostgreSQL 会因事务可见性规则(如 MVCC)要求 Session B 等待 Session A 提交/回滚——而 Session A 又在等待 Session B 完成才能继续,形成跨会话资源依赖,最终表现为不可恢复的阻塞(常被误称为“死锁”,实为锁等待超时前的无限挂起)。
✅ 正确做法:*所有与事务强相关的读写操作,必须统一通过 `sql.Tx` 实例执行**:
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
defer func() {
if r := recover(); r != nil || err != nil {
tx.Rollback()
}
}()
_, err = tx.Exec("INSERT INTO users(name) VALUES($1)", "alice")
if err != nil {
log.Fatal(err)
}
// ✅ 使用 tx.Query,确保在同一事务会话中执行
rows, err := tx.Query("SELECT id FROM users WHERE name = $1", "alice")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// 处理结果...
var id int
for rows.Next() {
err := rows.Scan(&id)
if err != nil {
log.Fatal(err)
}
}
_, err = tx.Exec("INSERT INTO logs(user_id, action) VALUES($1, $2)", id, "created")
if err != nil {
log.Fatal(err)
}
err = tx.Commit()
if err != nil {
log.Fatal(err)
}⚠️ 关键注意事项:
- *sql.Tx 不可复用:一旦 Commit() 或 Rollback() 调用后,该事务对象即失效,后续调用将 panic;
- 连接池管理无需手动干预:*sql.DB 负责高效复用底层连接,Begin() 自动从池中获取独占连接并绑定到事务生命周期;
- 避免混合使用:切勿在事务进行中穿插 db.Query / db.Exec —— 即使语义上看似“只读”,也可能因锁机制(如 SELECT ... FOR SHARE)、索引扫描或触发器引发隐式阻塞;
- 超时控制:建议为事务设置上下文超时(db.BeginTx(ctx, nil)),防止长期挂起影响服务可用性;
- 日志与可观测性:启用 pq 驱动的 log_level 参数(如 ?sslmode=disable&log_level=2)有助于诊断会话级锁等待。
总结:这不是 Go 或 lib/pq 的 Bug,而是对 SQL 事务隔离模型的误用。始终遵循 “一个事务,一个会话,一套 API” 原则——所有操作统一走 *sql.Tx 方法,即可彻底规避此类跨会话阻塞问题。

















