[英]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 (byinsertPersonFixtures
).此外,我希望 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.