Postgresql 维护同一行的并发更新的完整性
在下面的代码片段中,我尝试查找、删除和创建同一项,但在两个不同线程中的两个不同事务中 在线程1中,我创建事务1,找到该项并将其删除 完成后,我允许线程2创建事务2,并尝试查找该项。Postgresql 维护同一行的并发更新的完整性,postgresql,go,transactions,go-gorm,transaction-isolation,Postgresql,Go,Transactions,Go Gorm,Transaction Isolation,在下面的代码片段中,我尝试查找、删除和创建同一项,但在两个不同线程中的两个不同事务中 在线程1中,我创建事务1,找到该项并将其删除 完成后,我允许线程2创建事务2,并尝试查找该项。Find()方法在这里阻塞,因为我使用了这个选项 回到线程1,重新创建项目并提交事务1,这允许线程2中的Find()完成。以下是由此产生的问题: 如果我使用隔离级别“ReadCommitted”,我会得到一个找不到的错误-这对我来说毫无意义,因为我认为ReadCommitted事务可以看到其他人应用的更新 如果使用隔离
Find()
方法在这里阻塞,因为我使用了这个选项
回到线程1,重新创建项目并提交事务1,这允许线程2中的Find()
完成。以下是由此产生的问题:
如果我使用隔离级别“ReadCommitted”,我会得到一个找不到的错误-这对我来说毫无意义,因为我认为ReadCommitted事务可以看到其他人应用的更新
如果使用隔离级别“Serializable”,则会出现错误:pq:由于并发更新而无法序列化访问
为什么我会看到这种行为?我认为在第二次查找unblocks之后,它应该为我提供最新的行
如何使行在修改过程中,任何其他读取都将锁定,并在其他线程中完成时解锁返回最新数据
db,err:=gorm.Open(“postgres”,“host=localHost-port=5432 user=postgres-dbname=test-rm-password=postgres-sslmode=disable”)
如果出错!=无{panic(“连接数据库失败”)}
db.SingularTable(真)
db.DropTableIfExists(&Product{})
db.AutoMigrate(&Product{})
db.Create(&产品{代码:“A”,价格:1000})
//SQL:在“产品”(“代码”、“价格”)中插入返回“产品”的值('A',1000)。“id”
txOpt:=&sql.TxOptions{Isolation:sql.LevelSerializable}
doneTrans1:=make(chan结构{})
go func(){
项1:=&产品{}
tx1:=db.BeginTx(context.Background(),txOpt)
err=tx1.Set(“gorm:query_选项”,“FOR UPDATE”).Find(item1,“code=?”,“A”).Error
//SQL:从“产品”中选择*进行更新,其中(代码='A')
项目1.价格=3000
err=tx1.Delete(item1).错误
//SQL:从“产品”中删除,其中“产品”。“id”=1
doneTrans1也许我理解错了-我以前没有用过gorm。
但是,从您的查询注释中,两个goroutine中的两个事务都有一个“SELECT..FOR UPDATE”,并且它们并行运行。在尝试“SELECT..FOR UPDATE”相同的行之前,主goroutine没有等待在第二个goroutine中启动的事务提交
根据您的解释,可能是您在第二个goroutine中错误地包含了“FOR UPDATE”
或者您可以在第二个goroutine中使用锁,并在提交后释放它。而主goroutine等待获取锁,然后才执行其查询。也许我理解错了-我以前从未使用过gorm。
但是,从您的查询注释中,两个goroutine中的两个事务都有一个“SELECT..FOR UPDATE”,并且它们并行运行。在尝试“SELECT..FOR UPDATE”相同的行之前,主goroutine没有等待在第二个goroutine中启动的事务提交
根据您的解释,可能是您在第二个goroutine中错误地包含了“FOR UPDATE”
或者,您可以在第二个goroutine中使用锁,并在提交后释放它。而主goroutine等待获取锁,然后才执行其查询。要回答这个问题,我认为最好消除goroutine的复杂性(事实上,完全可以),并将重点放在SQL上。以下是SQL语句的运行顺序(我忽略了错误发生后的所有内容,因为这基本上是不相关的,而且执行顺序变得复杂/可变!)
在主程序中
INSERT INTO "product" ("code","price") VALUES ('A',1000) RETURNING "products"."id"
BEGIN TX1
SELECT * FROM "product" WHERE (code = 'A') FOR UPDATE
DELETE FROM "product" WHERE "product"."id" = 1
BEGIN TX2
SELECT * FROM "product" WHERE (code = 'A') FOR UPDATE -- ERROR occurs here
在GoRoutine中
INSERT INTO "product" ("code","price") VALUES ('A',1000) RETURNING "products"."id"
BEGIN TX1
SELECT * FROM "product" WHERE (code = 'A') FOR UPDATE
DELETE FROM "product" WHERE "product"."id" = 1
BEGIN TX2
SELECT * FROM "product" WHERE (code = 'A') FOR UPDATE -- ERROR occurs here
在主程序中
INSERT INTO "product" ("code","price") VALUES ('A',1000) RETURNING "products"."id"
BEGIN TX1
SELECT * FROM "product" WHERE (code = 'A') FOR UPDATE
DELETE FROM "product" WHERE "product"."id" = 1
BEGIN TX2
SELECT * FROM "product" WHERE (code = 'A') FOR UPDATE -- ERROR occurs here
请回答你的问题
问题1
如果我使用隔离级别“ReadCommitted”,我会得到一个找不到的错误-
这对我来说毫无意义,因为我认为
事务可以看到其他人应用的更新
从以下文件:
更新、删除、选择用于更新和选择用于共享命令
在搜索目标行方面的行为与SELECT相同:它们
将仅查找在命令start时提交的目标行
但是,这样的目标行可能已经更新(或
被另一个并发事务删除或锁定)
在这种情况下,可能的更新程序将等待第一次更新
正在更新要提交或回滚的事务(如果仍处于
如果第一个更新程序回滚,则其效果为
否定,第二个更新程序可以继续更新
最初找到行。如果第一个更新程序提交,则第二个更新程序
如果第一个更新程序删除了该行,则将忽略该行,否则将忽略该行
尝试将其操作应用于行的更新版本
因此,TX2中用于更新的SELECT*FROM“product”,其中(code='A')将等待TX1完成。此时TX1已删除产品A,因此该行将被忽略,不会返回任何结果。现在我知道TX1也会重新创建产品A,但请记住“SELECT查询(没有FOR UPDATE/SHARE子句)只查看在查询开始之前提交的数据;”,并且由于select在TX1重新创建记录之前开始,因此不会看到该记录
问题2
如果我使用隔离级别“Serializable”,我会得到错误:pq:can
由于并发更新,无法序列化访问
从文档(Serializable是一个更高的级别,因此这些规则以及一些更严格的规则适用):
更新、删除、选择用于更新和选择用于共享命令
在搜索目标行方面的行为与SELECT相同:它们
将仅查找在事务结束时提交的目标行
开始时间。但是,这样的目标行可能已经更新
(o)