简体   繁体   English

从 go 同时测试`FOR UPDATE`

[英]Test `FOR UPDATE` concurrently from go

I have a procedure with SELECT FOR UPDATE lock.我有一个带有SELECT FOR UPDATE锁的过程。 I would like to test it from go concurrently in order to make sure that lock is actually there.我想同时从 go 测试它,以确保锁确实存在。

I am using this:我正在使用这个:

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;

That's pretty dump sample I can provide at this time, but it shows that there is lock must be acquired to queue sp calls.这是我目前可以提供的非常漂亮的转储示例,但它表明必须获取锁才能将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
}

Also, I expect 2 goroutines to see test data populated in first transaction, but sp() fails because it does not see data, which is odd because read uncommitted level means it can see dirty data (by insertPersonFixtures ).此外,我希望 2 个 goroutines 看到第一个事务中填充的测试数据,但sp()失败,因为它没有看到数据,这很奇怪,因为读取未提交级别意味着它可以看到脏数据(通过insertPersonFixtures )。

A by @Brits:来自@Brits:

The SQL standard defines one additional level, READ UNCOMMITTED. SQL 标准定义了一个附加级别,READ UNCOMMITTED。 In PostgreSQL READ UNCOMMITTED is treated as READ COMMITTED.在 PostgreSQL 中,READ UNCOMMITTED 被视为 READ COMMITTED。

What is wrong with this code or perhaps it's a bad way to test RDBMS locks this way?这段代码有什么问题,或者以这种方式测试 RDBMS 锁可能是一种糟糕的方式? Or I misunderstood isolation levels?还是我误解了隔离级别? In my example I expect the 2nd goroutine to timeout and signal that there is a lock exist.在我的示例中,我希望第二个 goroutine 超时并发出存在锁的信号。

Is there a straightforward way to clean up database after each test?每次测试后是否有一种简单的方法来清理数据库? I am not sure running truncate for each table is easy.我不确定为每个表运行 truncate 是否容易。 Obviously after such idiocy from equal read committed and uncommitted and lack of proper nested transactions it all turns into a mess.显然,在平等读取提交和未提交以及缺乏适当的嵌套事务的这种白痴之后,一切都变成了一团糟。

From the PostgreSQL docs :来自PostgreSQL 文档

The SQL standard defines one additional level, READ UNCOMMITTED. SQL 标准定义了一个附加级别,READ UNCOMMITTED。 In PostgreSQL READ UNCOMMITTED is treated as READ COMMITTED.在 PostgreSQL 中,READ UNCOMMITTED 被视为 READ COMMITTED。

So it looks like what you are seeing is to be expected (however as you did not provide much info on sp() its difficult to comment - providing a Minimal, Reproducible Example might result in a better answer).因此,您所看到的似乎是意料之中的(但是,由于您没有提供有关sp()太多信息,因此很难发表评论-提供最小的、可重现的示例可能会得到更好的答案)。

Note you have a typo in tx.Exec("CAL sp()") - checking for errors returned from the various database calls would improve this test case.请注意,您在tx.Exec("CAL sp()")有一个错字 - 检查从各种数据库调用返回的错误将改进此测试用例。

Additional info following your update:更新后的其他信息:

tx.ExecContext(ctx, "CALL sp(`john`, 20)") should be err = tx.ExecContext(ctx, "CALL sp(`john`, 20)") (otherwise you are checking the error returned by begin transaction - best to check both). tx.ExecContext(ctx, "CALL sp(`john`, 20)")应该是err = tx.ExecContext(ctx, "CALL sp(`john`, 20)") (否则你正在检查 begin 返回的错误交易 - 最好同时检查两者)。

You can perform your test by setting a short timeout (say one second - something like tx.ExecContext(ctx, "SET statement_timeout = 1000) ) before doing the second update and then check that the call fails. this will work because the first transaction will maintain a lock until the second one is committed/rolled back.您可以在进行第二次更新之前通过设置短超时(比如一秒 - 类似于tx.ExecContext(ctx, "SET statement_timeout = 1000) )来执行测试,然后检查调用是否失败。这将起作用,因为第一个事务将保持一个锁直到第二个被提交/回滚。

Is there a straightforward way to clean up database after each test?每次测试后是否有一种简单的方法来清理数据库?

This depends on your requirements;这取决于您的要求; in this case rolling back the transactions will remove your test data.在这种情况下,回滚事务将删除您的测试数据。 Truncate works OK but generally you would want some test data to remain so it's not ideal.截断工作正常,但通常您希望保留一些测试数据,因此它并不理想。 I generally restore a backup or use a docker container (data restored as part of the build).我通常会恢复备份或使用 docker 容器(作为构建的一部分恢复的数据)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在MySQL中同时更新不同的行 - How to update different rows concurrently in Mysql 如何在没有锁的情况下并发更新PostgreSQL 13表行 - How to concurrently update PostgreSQL 13 table row without locks 同时从PostgreSQL获取唯一的序列号 - Obtain an unique sequence order number concurrently from PostgreSQL 从包含数字列表的文件中同时获取唯一编号 - Fetch a Unique Number from a file containing list of numbers concurrently postgreSQL 同时将列类型从 int 更改为 bigint - postgreSQL concurrently change column type from int to bigint 如何在Go中模拟SQL更新 - How to mock sql update in go 如何测试 CONCURRENTLY 是否用于刷新我的应用程序中的物化视图? - How can I test that CONCURRENTLY is being used to refresh a materialised view in my application? 具有唯一约束的 INSERT ON CONFLICT DO UPDATE SET(UPSERT)语句在同时运行时会产生约束冲突 - INSERT ON CONFLICT DO UPDATE SET (an UPSERT) statement with a unique constraint is generating constraint violations when run concurrently 如何在PostgreSQL中同时运行多个非原子计算的多个客户端中防止更新异常? - How to prevent update anomalies with multiple clients running non-atomic computations concurrently in PostgreSQL? Makefile:运行命令“go test ./...”后终止 - Makefile: Terminates after running commands “go test ./…”
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM