如何在 Java 应用程序中设置锁定模式
Posted
技术标签:
【中文标题】如何在 Java 应用程序中设置锁定模式【英文标题】:How to SET LOCK MODE in java application 【发布时间】:2020-07-02 16:16:50 【问题描述】:我正在开发一个使用 Weblogic 连接到 Informix 数据库的 Java Web 应用程序。在应用程序中,我们有多个线程在表中创建记录。
它经常发生失败并引发以下错误:
java.sql.SQLException: Could not do a physical-order read to fetch next row....
Caused by: java.sql.SQLException: ISAM error: record is locked.
我假设当记录被锁定时两个线程都在尝试插入或更新。
我做了一些研究,发现有一个选项可以设置数据库,而不是抛出错误,它应该等待锁被释放。
SET LOCK MODE TO WAIT;
SET LOCK MODE TO WAIT 17;
我认为 JDBC 中没有使用此设置的选项。如何在我的 java web 应用程序中使用此设置?
【问题讨论】:
你试过在Statement
中执行吗?
@Kayaman 我该怎么做?
你知道的,oldschool way。
@Kayaman 我会调查的。谢谢。
【参考方案1】:
您始终可以直接发送该 SQL,使用 createStatement()
,然后发送该确切的 SQL。
解决这个问题的更“正常”/现代的方法是 MVCC、事务级别“SERIALIZABLE”、重试和随机退避的组合。
不过,我不知道 Informix 是否接近那么先进。现代数据库,如 Postgres(就 MVCC/可序列化/重试/退避和事务安全而言,mysql 不算现代)。
在原始 JDBC 中做 MVCC/Serializable/Retry/Backoff 非常复杂;使用 JDBI 或 JOOQ 等库。
MVCC:事务是底层数据的浅层克隆的一种机制。 2 个单独的事务可以在同一个表中读取和写入相同的记录,而不会相互妨碍。在您提交事务之前,事情不会“保存”。
SERIALIZABLE:事务级别(也称为隔离级别),可使用jdbcDbObj.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
设置 - 最安全的级别。如果您知道版本控制系统是如何工作的:您要求数据库积极地重新调整所有内容,以便将整个提交链排序为一长串事件:每个事务的行为就像在前一个事务完成后完成一样.实现这个级别最简单的方法是全局锁定所有的东西。当然,这对多线程性能非常不利。在实践中,好的数据库引擎(例如 postgres)比这更聪明:多个线程可以同时运行事务,而不仅仅是被冻结和等待锁;相反,数据库引擎会检查事务所做的事情(不仅是写入,还有读取)是否与同时的事务无冲突。如果是,这一切都是允许的。如果不是,除了一个同时的事务之外的所有事务都会抛出重试异常。这是唯一可以让您安全地完成这一系列事件的关卡:
-
获取 isaace 银行账户的余额。
获取 rzwitserloot 银行账户的余额。
从isaace 的号码中减去10 欧元,如果余额不足则失败。
在 rzwitserloot 的号码上加 10 欧元。
将 isaace 的新余额写入数据库。
将 rzwitserloot 的新余额写入数据库。
提交事务。
任何低于 SERIALIZABLE 的级别都会默默地失败;如果多个线程同时执行上述操作,则不会发生 SQLExceptions,但 isaace 和 rzwitserloot 的余额总和会随着时间而变化(金钱丢失或创建 - 在步骤 1 和 2 与步骤 5/6/7 之间,另一个线程设置新余额,但这些新余额因 5/6/7 的更新而丢失)。使用可序列化,这是不可能发生的。
RETRY:智能数据库解决问题的方法是通过检查整个事务完成的所有 SELECT 是否不受提交到数据库的任何事务的影响,使除一个事务之外的所有事务都失败(出现“重试”错误) 在此交易打开之后。如果答案是肯定的(某些选择会有所不同),则交易失败。这个错误的重点是告诉运行事务的代码只是..从顶部开始并再次执行。这次很可能不会发生冲突,并且会起作用。假设是冲突可能发生但通常不会发生,所以最好假设“天气晴朗”(没有锁,只是做你的事情),事后检查,然后在它发生冲突的异国场景中再试一次,而不是尝试锁定行和表。请注意,例如以太网的工作方式相同(假设天气晴朗,之后恢复错误)。
BACKOFF:重试的一个问题是计算机过于一致:如果 2 个线程相互妨碍,它们都可能失败,都重试,只是再次失败,永远。解决方案是线程在 随机 时间内旋转他们的拇指,以确保在某个时候,两个冲突的重试器之一“获胜”。
换句话说,如果你想做到“正确”(参见银行账户示例),但也相对“快速”(不是全局锁定),请获得可以做到这一点的 DB,并使用 JDBI 或 JOOQ;否则,您必须编写代码以在 lambda 块中运行所有数据库内容,捕获 SQLException,检查 SqlState 以查看它是否指示您应该重试(sqlstate 代码是特定于数据库引擎的),如果是,在等待指数增加的时间后重新运行该 lambda,其中还包括一个随机因素。这相当复杂,这就是为什么我强烈建议您依靠 JOOQ 或 JDBI 来为您解决这个问题。
如果您还没有准备好使用该级别的数据库,只需声明并发送“SET LOCK MDOE TO WAIT 17;”作为 SQL 语句,在打开任何连接的开始。如果您使用的是连接池,通常有一个地方可以配置 SQL 语句以在连接启动时运行。
【讨论】:
感谢您长时间详细的回答。它高于我的数据库使用水平。我先试试简单的方法。【参考方案2】:Informix JDBC 驱动程序允许您在连接到服务器时自动设置锁定等待模式。
只需通过 DataSource 或连接 URL 传递以下参数
IFX_LOCK_MODE_WAIT=17
JDBC 的值为
(-1) 永远等待 (0) 不等待(默认) (> 0) 等待这么多秒见https://www.ibm.com/support/knowledgecenter/SSGU8G_14.1.0/com.ibm.jdbc.doc/ids_jdbc_040.htm
【讨论】:
【参考方案3】:连接连接 = DriverManager.getConnection("jdbc:Informix-sqli://cleo:1550: IFXHOST=cleo;PORTNO=1550;user=rdtest;password=my_passwd;IFX_LOCK_MODE_WAIT=17";);
【讨论】:
以上是关于如何在 Java 应用程序中设置锁定模式的主要内容,如果未能解决你的问题,请参考以下文章
如何在 postgres 中设置锁定超时 - Hibernate
如何在 swift UITests 中的 XCUIApplication 中设置暗模式?