Golang 并发 SQL 事务

Kae*_*dys 5 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()
Run Code Online (Sandbox Code Playgroud)

这发生在单元测试中。这个想法是强制并发失败,因为两个 Exec 正在尝试更新同一行,以确保给出正确的冲突错误响应。然而,第二个 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 驱动程序如何处理连接池的信息?为什么第二个事务 Exec 会阻塞,直到第一个事务提交为止?事务的要点不就是两者可以同时运行,并且谁先提交(或开始?)谁获胜?

Kae*_*dys 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。第二个更新/插入/删除将阻塞,直到调用第一个更新/插入/删除的事务调用 COMMIT 或 ROLLBACK。