如何让 Firebird 客户端应用程序等待行解锁
Posted
技术标签:
【中文标题】如何让 Firebird 客户端应用程序等待行解锁【英文标题】:How to make Firebird client application wait for row to unlock 【发布时间】:2015-08-03 11:28:11 【问题描述】:我熟悉使用 ADO (dbGo) 的 Microsoft SQL 服务器世界,并且我为该环境编写了许多应用程序。现在我有一个旧版 Delphi 7 应用程序,其中包含我必须维护的 Firebird 2.5 数据库。
但我发现如果 2 个客户端应用程序执行此操作:
SQLQuery.SQL.Text := 'Update mytable set field1 = 11 where keyfield = 99'
SQLQuery.Execute;
几乎在同一时间,第二个应用程序立即出现“死锁”错误。在 SQL Server 中,会有一个等待期
ADOConnection.Isolationlevel = ilCursorstability;
ADOConnection.CommandTimeout := 5;
在第二个客户端应用程序中引发任何异常之前。异常处理可能涉及在批处理过程中被视为非常不寻常的情况下的回滚。这是合理。 5 秒对于计算机处理时间来说是非常长的时间。
现在我在 Firebird 客户端上使用相同方法的尝试没有结果,因为“死锁”(实际上是正在使用的记录)立即发生。
如果无法将数据库引擎配置为等待一段时间来改善情况(释放记录锁),那么现在的责任必须落在客户端应用程序开发人员身上,他们必须编写极其缓慢的代码来克服在我看来的问题是火鸟的主要失败。
一旦检测到“死锁”,除非断开连接组件,否则条件不会清除
while rowsupdated = 0 and counter < 5 do
begin
try
rowsupdated := SQLQuery.Execute;
except
SQLConnection.Connected := False;
SQLConnection.Connected := True;
end;
Inc(Counter)
end;
当您在 Firebird 中没有任何实质性的锁容错时,如何使用 Delphi 中的 DBX 来制作强大的多用户表更新客户端?
【问题讨论】:
有一段时间没有使用 FirebirdSQL,但请记住,有一个 SELECT FOR UPDATE WITH LOCK 功能可以在 SQL 级别使用。结帐:firebirdsql.org/refdocs/langrefupd25-notes-withlock.html DBExpress 到 Interbase 连接的 IsolationLevel 默认值为ReadCommitted
,相当于ilCursorstability
。 CommandTImeout
不存在,但是有WaitOnLocks
,默认为True
,意思是指定一个事务在遇到与另一个事务的锁冲突时等待访问(根据文档)。这两个都在连接的参数中设置。
尽管我尝试以不同方式配置客户端,但它似乎默认为nowait
。请参阅下面我对@TOndrej 的问题。
【参考方案1】:
客户端可以指定事务是否应该等待死锁解决。如果在您的情况下死锁立即发生,则可能是由于您的配置(在客户端上使用 nowait
事务参数)。不使用nowait
将导致服务器端检测到死锁并(在可配置的超时之后)在客户端引发异常。
由于Firebird 2.0,您还可以从客户端指定事务的锁定超时,覆盖服务器配置的超时值。
【讨论】:
你能说得更具体些吗?如何在客户端为特定应用程序指定锁定超时值? @nolaspeaker 这取决于您选择的客户端库以及它如何公开需要在 Firebird 客户端 API 级别设置的事务参数块 (TPB)。 我明白了,您正在使用 DBX。哪个版本/Delphi 版本? Delphi 7. 我看到客户端库名为 dbxup_fb.dll,来自 Upscene 制作版本 2.3.0.2 2010-03-8。莫非这个组合(带fbclient.dll)不支持锁超时值? @nolaspeaker 可能是。我对这个 Firebird DBX 驱动程序没有经验(事实上,根本没有 Firebird DBX 驱动程序)。到目前为止,我只在 Firebird 中使用过 IBObjects 和 FreeIB 库。【参考方案2】:Firebird 事务可以配置为 nowait 或 wait(有或没有特定超时)。如何配置取决于驱动程序,由于我不熟悉 Delphi,我无法对此发表评论。 Nowait 通常是默认设置,因为在大多数情况下等待只会延迟不可避免的事情。
“死锁”错误有点用词不当,因为它不是正常并发术语中的死锁。错误的第二部分通常更具描述性,例如更新与并发更新冲突。,它被归类为“死锁”是一个历史性的工件(尽管在大多数情况下,这个错误意味着您需要重新开始你的交易,所以从这个意义上说它是“死的”)。
【讨论】:
【参考方案3】:我正在使用“回答您自己的问题”按钮。我发现了一个解决方案。
-
为 Firebird/Interbase 安装 IBPhoenix 开源 ODBC 驱动程序
将 ODBC DSN 配置为连接到 Firebird.fdb 并选中 nowait,并根据需要设置 LockTimeout(以秒为单位)。我选择了 15 秒。
使用配置为使用 Microsoft OLE DB Provider for ODBC 驱动程序的 Delphi 7 ADO (dbGo) TADOConnection。
这是重要的一点:将 ADOConnection.TransactionIsolation 设置为 ilReadUncommited 或 ilDirtyRead。
如果发现记录已在尚未提交的事务中更新,则它会导致 TADOQuery.ExecSQL 实际上等待(最多指定 15 秒)或回滚!
这与 DBX 驱动程序不同,后者在这种情况下会立即引发所谓的“死锁”异常。 (正如我们上面讨论的)
所以,如果两个查询都这样做
Update MYTABLE set NUM = NUM + 1 where keyvalue = 99;
并且起始值(在任何更新之前)为 0,两个事务提交后的 NUM 值为 2,正如预期的那样。
从 NUM = 0 重新开始。如果第一个事务回滚,则第二个事务可以提交(或回滚)。而第二次更新提交后的值只有 1。
我不知道它是如何或为什么这么好用的,特别是因为 Firebird 不应该支持 ReadUnComitted 或 DirtyRead,但我很高兴它按我想要的方式工作。
【讨论】:
以上是关于如何让 Firebird 客户端应用程序等待行解锁的主要内容,如果未能解决你的问题,请参考以下文章