Go语言并发SQL事务

5

在并发和SQL事务方面遇到了问题。我有以下(存根)代码(为清晰起见删除了错误检查等):

dao, _ := sql.Open("postgres", args)

tx1 := dao.Begin()
res, _ := tx1.Exec("UPDATE <...>", args...)
// error check

tx2 := dao.Begin()
res, _ = tx2.Exec("UPDATE <same>", args...)

_ = tx1.Commit()

_ = tx2.Commit()

这是在单元测试中发生的。其想法是强制并发出现故障,因为两个“Execs”试图更新同一行,以确保给出正确的冲突错误响应。然而,第二个“Exec”会永久阻塞。
据我所知,只有数据库连接用完时才会出现这种类型的阻塞,但是事务都应该在它们自己的(独占的)连接中运行,并且我没有在任何地方设置最大连接数(我还尝试将其设置为诸如100之类的值,但没有效果)。
这里是奇怪的部分。如果我将tx2.Exec()和Commit()分离到一个带有同步通道的goroutine中,以阻止主线程运行tx1.Commit(),直到tx2运行了它的Exec(),同样的事情会发生,在tx2.Exec()上无限期阻塞。如果我使用time.Sleep()代替同步通道,则tx2.Exec会阻塞,直到休眠完成并运行tx1.Commit(),然后完成并显示预期错误(pq: could not serialize access due to concurrent update
我是否对golang的SQL包或postgres驱动程序处理连接池的方式漏掉了什么?为什么第二次事务执行会被阻塞,直到第一个事务提交?事务不是两者可以同时运行,先提交(或开始?)的那个获胜吗?

你在使用哪个Postgres的包?我认为你的问题与我曾经删除的回答类似,但我的措辞肯定是不对的。从“Open”返回的*DB对象可用于管理多个连接,您的驱动程序可能限制为一个连接,或者并不设计打开新连接,每个驱动程序都有自己的实现,它们的列表可以在这里找到:https://github.com/golang/go/wiki/SQLDrivers - evanmcdonnal
1
我已经查看了源代码,没有发现任何限制。一般来说,从我所了解的情况来看,驱动程序鼓励让golang处理池。 - Kaedys
1
嗯,我不确定,但是如果你在这里查看源代码,似乎大部分责任在于驱动程序;https://golang.org/src/database/sql/sql.go?s=5752:6728#L211 - evanmcdonnal
1个回答

4
经过进一步研究,看起来这实际上不是Golang、SQL或PQ包的问题。这是PostgreSQL内在的(并且是有意设计的)行为:
"UPDATE、DELETE、SELECT FOR UPDATE和SELECT FOR SHARE命令在搜索目标行方面与SELECT相同:它们只会找到作为命令开始时间提交的目标行。然而,这样的目标行可能已经被另一个并发事务更新(或删除或锁定)在找到它时。在这种情况下,想要更新者将等待第一个更新事务提交或回滚(如果它仍在进行中)。”

http://www.postgresql.org/docs/9.1/static/transaction-iso.html

因此,阻塞发生在Postgres中,而不是Go中。 可以通过在单独的终端中运行并发事务来确认这一点,这些事务使用psql更新(或插入或删除等)相同记录。第二个Update/Insert/Delete将被阻塞,直到调用第一个操作所在的事务调用COMMIT或ROLLBACK。


顺便提一下,如果你想让它在无法获取锁时不等待,那么可以在查询中添加“NOWAIT”。来源:https://dev59.com/sa3ns4cB2Jgan1zn_xV5 - Matthew Clark
哦,这也很方便。我得记住这个。谢谢,@MatthewClark! - Kaedys

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接