txdb不能真正测试事务隔离级别,因为它仅用内存模拟事务,不启动真实数据库进程,也不执行锁、MVCC或WAL,无法触发并发读写、幻读、不可重复读等隔离现象。

为什么 txdb 不能真正测试事务隔离级别
因为 txdb 只是用内存模拟事务行为,它不启动真实数据库进程,也不执行实际的锁、MVCC 或 WAL 流程。你调用 Begin()、Commit()、Rollback() 看似正常,但并发读写、幻读、不可重复读等现象根本不会触发——底层压根没做隔离控制。
- 它适合快速验证 SQL 语法、结构体 Scan 是否匹配,不适合测事务语义
- 当你看到 “测试通过”,其实只是验证了代码没 panic,不是事务逻辑正确
- PostgreSQL 的
READ COMMITTED和REPEATABLE READ行为,在txdb里全被简化成“最后提交赢”
用真实数据库跑事务并发测试的最小可行方案
必须起一个本地数据库实例(比如 Docker 启的 PostgreSQL),再用 Go 启多个 goroutine 模拟并发事务。关键不是“能不能连上”,而是“能否观察到预期的隔离失效或阻塞”。
- 用
docker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=pass postgres:15快速拉起环境 - 每个 goroutine 单独开
*sql.Tx,显式调用tx.QueryRow和tx.Commit() - 不要用
db.QueryRow—— 它走的是自动提交模式,事务边界消失 - 加
time.Sleep在关键位置(如 Read-Modify-Write 中间),才能让竞态窗口可观察
tx1, _ := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
tx2, _ := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
sql.TxOptions 的 Isolation 值在不同驱动下表现不一致
Go 标准库只定义了几个常量,但具体是否生效、如何映射到数据库原生命令,完全取决于 driver 实现。比如 pgx 会发 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ,而老版本 lib/pq 对 LevelRepeatableRead 直接忽略,降级成 READ COMMITTED。
- 务必检查你用的 driver 文档,确认它支持你要测的隔离级别
- PostgreSQL 实际只有
READ COMMITTED和REPEATABLE READ(后者实为 Serializable 的弱化版) - MySQL 的
READ UNCOMMITTED在go-sql-driver/mysql中需手动拼 SQL:tx.Exec("SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED") - 测试前用
tx.QueryRow("SHOW TRANSACTION ISOLATION LEVEL")实际查一遍,别信常量名
怎么判断一次事务测试真的“测到了问题”
核心是制造可验证的状态冲突:两个事务操作同一行,且结果违反了所选隔离级别的承诺。光看有没有 panic 或 error 不够,得比对最终数据库状态和各事务中间观测值。
立即学习“go语言免费学习笔记(深入)”;
- 在事务 A 中
SELECT balance FROM accounts WHERE id = 1得到 100 - 在事务 B 中更新同一行并
Commit(),balance 变成 80 - 事务 A 再
SELECT,如果仍是 100 → 符合REPEATABLE READ;如果是 80 → 实际运行在READ COMMITTED - 用
tx.QueryRow而非db.QueryRow,否则第二次 SELECT 已不在事务上下文中 - 别依赖日志输出判断——并发时 goroutine 打印顺序不可靠,只认最终 DB 值和
err返回
事务测试最难的不是写代码,是设计那个刚好卡在隔离边界上的时间差。多试几次,加点 time.Sleep(5 * time.Millisecond),比猜更可靠。










