并发更新死锁,但我看不到并发

Posted

技术标签:

【中文标题】并发更新死锁,但我看不到并发【英文标题】:Deadlock on concurrent update, but I can see no concurrency 【发布时间】:2015-04-29 01:23:45 【问题描述】:

当只有一个事务写入数据库时​​,什么会在 Firebird 上触发死锁消息?

我正在基于 Firebird 2.1 数据库构建一个后端用 Delphi2010 编写的 webapp。我收到一个我无法理解的并发更新错误。也许有人可以帮助我调试问题或解释可能导致该消息的场景。

我正在尝试对单个记录上的单个字段进行更新。

UPDATE USERS SET passwdhash=? WHERE (RECID=?)

我看到的消息是标准的:

deadlock
update conflicts with concurrent update
concurrent transaction number is 659718
deadlock
Error Code: 16

我明白它告诉我的内容,但我不明白为什么我会在这里看到它,因为我知道没有并发更新。

这是我所做的调查。

我启动了我的应用服务器并检查了这个查询的结果:

SELECT
   A.MON$ATTACHMENT_ID,
   A.MON$USER,
   A.MON$REMOTE_ADDRESS,
   A.MON$REMOTE_PROCESS,
   T.MON$STATE,
   T.MON$TIMESTAMP,
   T.MON$TOP_TRANSACTION,
   T.MON$OLDEST_TRANSACTION,
   T.MON$OLDEST_ACTIVE,
   T.MON$ISOLATION_MODE
FROM MON$ATTACHMENTS A
LEFT OUTER JOIN MON$TRANSACTIONS T
    ON (T.MON$ATTACHMENT_ID = A.MON$ATTACHMENT_ID)

结果表明有多个连接,但其中只有一个在 MON$TRANSACTION 字段中具有非空值。这个连接是我从 IBExperts 用来查询监控表的连接。

我是否认为可以忽略没有活动事务的连接不会导致死锁情况?

接下来,我在应用服务器中提交 UPDATE-Statement 的行上放置一个断点,并执行触发它的请求。当断点停止应用程序时,我重新运行上面的 Monitor-query。

这一次我可以看到另一个交易活动,正如我所期望的那样:

然后我让我的应用服务器执行 UPDATE 并获得如上所示的错误消息。

当只有一个写事务时,什么会触发死锁消息?或者还有更多,我误解了输出?有关如何调试此问题的任何其他建议?

【问题讨论】:

你可能想单独检查MON$TRANSACTIONS而不加入MON$ATTACHMENTS,并使用gfix检查是否有transactions in limbo。顺便说一句:死锁有点用词不当,它不是一般意义上的死锁:这里的意思是有一个较新的记录版本对您当前的事务不可见。 澄清一下:如果另一个事务修改了相同的记录并且该事务被提交之后,您也可能会收到此错误(取决于事务隔离,例如一致性或并发性)您的交易已开始。 我可以在触发稍后导致失败的 Webrequest 之前手动编辑 IBExpert 中的字段。所以它可能不是长时间卡住的东西,而是仅限于请求生命周期本身。在同一个请求中确实有另一个更新记录。不过,对于不同的领域。我应该知道这一点,但不要:这种并发检查是否基于每条记录进行?如果是这样,我有一个很好的领导...... 每次更新都会创建一个新版本的记录,所以如果在同一个请求中您在不同的事务中更改了相同的记录(即使它是不同的字段),那么这可能就是问题所在您当前的事务在其他事务提交之前开始。查看您使用 concurrency (=1) 作为事务隔离的屏幕截图,在这种情况下会触发此异常(在大多数情况下,使用 read committed record version 会 -不会出现此错误,但它放松了一些可能不是您想要的约束。 【参考方案1】:

Firebird 使用 MVCC (Multiversion Concurrency Control) 作为其事务模型。其中一个功能是 - 根据事务隔离 - 您只会看到事务开始时提交的最后一个版本(一致性并发隔离级别),或者当您的语句开始时已提交(读取已提交)。对记录的更改将创建记录的新版本,该版本仅在其他活动事务已提交时才可见(然后仅对已提交的读取事务可见)。

作为基本规则,一条记录只能有一个未提交的版本。因此,两个事务尝试更新同一记录的尝试对于其中一个事务将失败。由于历史原因,这些类型的错误被归为 deadlock 错误系列,即使在正常的并发语言中它实际上并不是死锁。

根据您的事务隔离,此规则实际上更具限制性:为了一致性和并发级别,也不能有对您的事务不可见的记录的更新提交版本。

我的猜测是你发生了这样的事情:

    事务 1 开始 事务 2 以 concurrencyconsistency 隔离开始 事务 1 修改记录(已创建新版本) 事务 1 提交 事务 2 尝试修改同一记录

(注意,第 1+3 步和第 2 步的顺序可能不同(例如 1,3,2 或 2,1,3))

第 5 步失败,因为在第 3 步中创建的新版本对事务 2 不可见。如果改为使用 read committed,则第 5 步将成功,因为新版本对事务 2 可见在那个时候进行交易。

【讨论】:

以上是关于并发更新死锁,但我看不到并发的主要内容,如果未能解决你的问题,请参考以下文章

并发请求 + 事务嵌套 + 更新数据 = 死锁

全面的乐观并发还是只是导致死锁的表?

如何更新输入值并发送到 Request Laravel?

(更新中)谈谈个人对java并发编程中(管程模型,死锁,线程生命周期等问题) 见解

并发编程--一堆锁,GIL,同步异步,Event事件

使用 NSURLSessionDownloadTask 设置并发下载次数