从 go 同时测试`FOR UPDATE`

我有一个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 是否容易。显然,在同等读取已提交和未提交以及缺乏适当的嵌套事务之后,这一切都变得一团糟。


月关宝盒
浏览 179回答 1
1回答

慕桂英546537

来自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 容器(作为构建的一部分恢复的数据)。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go