Golang 并发 SQL 事务

Posted

技术标签:

【中文标题】Golang 并发 SQL 事务【英文标题】:Golang Concurrent SQL Transactions 【发布时间】:2015-10-19 20:52:32 【问题描述】:

遇到并发和 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 永久阻塞。

据我所知,这种类型的阻塞只应该在数据库没有数据库连接时发生,但事务都应该在它们自己的(独占)连接中运行,我没有设置任何地方的最大连接数(我也尝试将其设置为 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 阻塞直到第一个事务被提交?事务的重点不是两者可以同时运行,谁先提交(或者是开始?)谁赢?

【问题讨论】:

postgres 使用什么包?我认为您的问题与我现在已删除的答案中的内容相似,但是我的语言肯定是错误的。从Open 返回的*DB 对象可用于管理多个连接,您的驱动程序很可能有一个限制或不设计为打开新连接每个驱动程序都有自己的实现,可以找到它们的列表这里; github.com/golang/go/wiki/SQLDrivers github.com/lib/pq 我查看了源代码,它似乎没有我能找到的任何限制。据我所知,通常鼓励司机让 golang 处理池化。 嗯我不确定,但如果你看这里的源代码,似乎大部分责任都在驱动程序上; golang.org/src/database/sql/sql.go?s=5752:6728#L211 【参考方案1】:

经过进一步研究,看起来这实际上不是 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 添加到查询中。来源:***.com/questions/23702284/… 噢噢噢,这也很方便。我必须记住这一点。谢谢,@MatthewClark!

以上是关于Golang 并发 SQL 事务的主要内容,如果未能解决你的问题,请参考以下文章

带有 golang 准备语句的原始 sql 事务

Golang执行sql事务

GoLang如何操作mysql

深入浅出Golang的协程池设计

一个sql盲注小工具 (Golang版)

golang redis事务 --- 2022-04-03