我有一个SELECT FOR UPDATE带锁的程序。我想同时测试它,以确保锁确实存在。我正在使用这个:CREATE TABLE IF NOT EXISTS person ( name varchar primary key);INSERT INTO person VALUES ('john');CREATE TABLE IF NOT EXISTS tickets ( name varchar PRIMARY KEY REFERENCES person, amount integer NOT NULL);CREATE OR REPLACE PROCEDURE sp (_name varchar, _amount integer) AS$$BEGIN -- acquire a lock on person row PERFORM name FROM person WHERE name = _name FOR UPDATE; INSERT INTO tickets VALUES(_name, _amount);END$$ LANGUAGE plpgsql;这是我目前可以提供的漂亮转储示例,但它表明必须获取锁才能对sp调用进行排队。func TestInsert(t *testing.T) { tx, err := db.Begin() // Read Committed level tx defer tx.Rollback() insertPersonFixtures(tx) // Using this tx to fill database with test data needed by testing SP ready1 := make(chan struct{}) ready2 := make(chan struct{}) done := make(chan struct{}) go func() { // Must see `prepareSomeData` data in database?? tx, err := db.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelReadUncommitted}) defer tx.Rollback() tx.Exec("CALL sp('john', 10)") ready1 <- struct{}{} <-ready2 done <- struct{}{} }() go func() { <-ready1 ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() // Must see `prepareSomeData` data in database?? tx, err := db.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelReadUncommitted}) defer tx.Rollback() tx.ExecContext(ctx, "CALL sp(`john`, 20)") if err == nil { t.Error("No lock", err) } ready2 <- struct{}{} }() <-done}另外,我希望 2 个 goroutine 可以看到在第一个事务中填充的测试数据,但是sp()因为看不到数据而失败,这很奇怪,因为未提交的读取级别意味着它可以看到脏数据(按insertPersonFixtures)。@Brits 的一个:SQL 标准定义了一个附加级别,READ UNCOMMITTED。在 PostgreSQL 中,READ UNCOMMITTED 被视为 READ COMMITTED。这段代码有什么问题,或者以这种方式测试 RDBMS 锁可能是一种不好的方法?还是我误解了隔离级别?在我的示例中,我希望第二个 goroutine 超时并发出存在锁的信号。每次测试后有没有一种简单的方法来清理数据库?我不确定为每个表运行 truncate 是否容易。显然,在同等读取已提交和未提交以及缺乏适当的嵌套事务之后,这一切都变得一团糟。
1 回答
慕桂英546537
TA贡献1848条经验 获得超10个赞
SQL 标准定义了一个附加级别,READ UNCOMMITTED。在 PostgreSQL 中,READ UNCOMMITTED 被视为 READ COMMITTED。
因此,您所看到的似乎是意料之中的(但是,由于您没有提供太多关于sp()
难以评论的信息 - 提供一个最小的、可重现的示例可能会产生更好的答案)。
请注意,您有一个错字tx.Exec("CAL sp()")
- 检查从各种数据库调用返回的错误将改进此测试用例。
更新后的附加信息:
tx.ExecContext(ctx, "CALL sp(`john`, 20)")
应该是err = tx.ExecContext(ctx, "CALL sp(`john`, 20)")
(否则您正在检查 begin transaction 返回的错误 - 最好同时检查)。
您可以通过在进行第二次更新之前设置一个短暂的超时(比如一秒 - 类似tx.ExecContext(ctx, "SET statement_timeout = 1000)
)来执行测试,然后检查调用是否失败。这将起作用,因为第一个事务将保持锁定,直到第二个事务被提交/回滚。
每次测试后有没有一种简单的方法来清理数据库?
这取决于您的要求;在这种情况下,回滚事务将删除您的测试数据。截断工作正常,但通常你会希望保留一些测试数据,所以它并不理想。我通常恢复备份或使用 docker 容器(作为构建的一部分恢复的数据)。
- 1 回答
- 0 关注
- 178 浏览
添加回答
举报
0/150
提交
取消