您可以找到很多关于如何在Go中防止死锁的文章,但大多数文章都集中在并发模式和诸如互斥锁之类的同步工具上。虽然了解一些防止它们的技术很重要,但数据库事务死锁是您更容易在不知不觉中遇到的陷阱。
当您启动一个或多个事务,并在事务仍处于活动状态时在事务外部运行查询时,可能会发生事务死锁。如果同时运行太多事务和查询,则连接池中的数据库连接可能会用完。下面是一个简单的例子:
//我们将忽略本例中的错误,//您应该始终检查Course.tx,_:=db.Begin()tx.Exec(`insert into";foo";(";a";,";b";)value(4,2)`)//...db.Query(`SELECT*from";foo";where";a";=$1 and。=$2`,4,2)//DEADLOCKtx.Commit()。
在本例中,我们创建了一个新事务并向数据库中插入了一些内容。稍后,我们尝试从数据库中查询相同的结果。插入的行尚未提交并不是实际问题,因为在这种情况下您不会收到任何结果。这里真正的问题是,如果并发运行此代码,可能会耗尽连接。可以配置打开多少个到数据库的连接。一旦您的代码到达db.query,最后一个连接可能会被事务占用,它们会阻塞最后一个连接,直到连接可用,而这可能永远不会发生。
那么你怎么解决这个问题呢?首先,对于代码的特定部分,所有查询都应该在事务外部或内部运行。即使您同时运行事务块和非事务块,非事务块也不会永远被事务阻塞(但非事务块可能需要等待另一部分完成)。此外,您可以使用Linter或其他工具来确保所有查询完全在事务内部或外部运行。
我通常针对数据库编写集成测试。如果您执行相同的操作,则可以配置连接池大小,以确保测试将仅使用单个连接。这样,如果您忘记在某个地方使用事务,测试将陷入死锁。您可以很容易地在包的TestMain函数中配置它。
我希望这能帮助您防止一些令人讨厌的死锁错误。通过限制连接池,我在更大的代码库中发现了相当多的代码。在生产中,您当然应该使用多个连接来加快速度。