为了账号安全,请及时绑定邮箱和手机立即绑定

从 go 同时测试`FOR UPDATE`

从 go 同时测试`FOR UPDATE`

Go
月关宝盒 2022-05-10 16:02:45
我有一个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个赞

来自PostgreSQL 文档

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 容器(作为构建的一部分恢复的数据)。


查看完整回答
反对 回复 2022-05-10
  • 1 回答
  • 0 关注
  • 178 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信