Golang并发SQL事务

时间:2015-10-19 20:52:32

标签: sql postgresql go

遇到并发和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()

这发生在单元测试中。这个想法是强制并发失败,因为两个Exec正在尝试更新同一行,以确保给出正确的冲突错误响应。但是,第二个Exec永久阻止。

据我所知,这种类型的阻塞只应该在DB不在数据库连接中时发生,但是事务都应该在他们自己的(独占)连接中运行,而我不是在任何地方设置最大连接(我也尝试将其设置为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驱动程序如何处理连接池?为什么第二个事务Exec会阻塞,直到第一个事务被提交?不是两个可以同时运行的交易点,无论哪个提交(或者它是否开始?)首先获胜?

1 个答案:

答案 0 :(得分:2)

经过进一步研究,看起来这实际上并不是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在单独的终端中运行更新(或插入或删除等)相同记录的并发事务来确认。第二个更新/插入/删除将阻塞,直到调用第一个的事务调用COMMIT或ROLLBACK。