Oracle 提交和选择之间存在滞后
我们有一个 Java 工作流应用程序,它使用 Oracle 数据库来跟踪其步骤以及与其他服务的交互。 在工作流运行期间,会执行多次插入/更新/选择,有时选择不会返回更新的数据,即使插入/更新提交在成功完成之前运行也是如此。 工作流程出错后(由于数据错误),如果我们返回并通过第三方应用程序检查数据库,则会显示新的/更新的数据。 我们的提交完成和可见之间似乎存在滞后。 这种情况发生在所有工作流运行的大约 2% 中,并且在数据库使用量很大时会增加。
我们的数据库支持团队建议将参数 max-commit-propagation-delay 更改为 0,因为它默认为 700。这似乎是一个可能的解决方案,但最终并没有解决我们的问题。
该应用程序在WebSphere 上运行,并且Oracle 数据库配置为JDBC 数据源。 我们使用的是 Oracle 10.1g。 该应用程序是用 Java 1.5 编写的。
任何帮助,将不胜感激。
编辑:示例代码
DataSource ds; // spring configured
String sql = "INSERT INTO " + currentTable + " (" + stepId + ',' + stepEntryId + ", " + stepStepId + ", " + stepActionId + ", " + stepOwner + ", " + stepStartDate + ", " + stepDueDate + ", " + stepFinishDate + ", " + stepStatus + ", " + stepCaller + " ) VALUES (?, ?, ?, null, ?, ?, ?, null, ?, null)";
Connection conn = ds.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql);
// set values
stmt.executeUpdate();
// close connections
// later on in the code...
Connection conn = ds.getConnection();
PreparedStatement stmt = null;
ResultSet rset = null;
String sql = "SELECT " + stepId + ", " + stepStepId + ", " + stepActionId + ", " + stepOwner + ", " + stepStartDate + ", " + stepDueDate + ", " + stepFinishDate + ", " + stepStatus + ", " + stepCaller + " FROM " + currentTable + " WHERE " + stepEntryId + " = ?";
stmt = conn.prepareStatement(sql);
stmt.setLong(1, entryId);
rset = stmt.executeQuery();
//close connections
We have an Java workflow application that uses an Oracle database to track its steps and interactions with other services. During a workflow run several insert/update/selects are performed and occasionally the select will not return the updated data, even though the insert/update commit that ran before it completed successfully. After the workflow errors out (due to the bad data), if we go back and check the database through a 3rd party app the new/updated data will show up. There seems to be a lag between when our commits go through and when they are visible. This happens in roughly 2% of all workflow runs and it increases during heavy database usage.
Our database support team suggested to change a parameter max-commit-propagation-delay to 0, as it defaulted to 700. This appeared to be a possible solution but ultimately did not fix our problem.
The application runs on WebSphere and the Oracle database is configured as a JDBC datasource. We are using Oracle 10.1g. The application is written in Java 1.5.
Any help would be appreciated.
edit: sample code
DataSource ds; // spring configured
String sql = "INSERT INTO " + currentTable + " (" + stepId + ',' + stepEntryId + ", " + stepStepId + ", " + stepActionId + ", " + stepOwner + ", " + stepStartDate + ", " + stepDueDate + ", " + stepFinishDate + ", " + stepStatus + ", " + stepCaller + " ) VALUES (?, ?, ?, null, ?, ?, ?, null, ?, null)";
Connection conn = ds.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql);
// set values
stmt.executeUpdate();
// close connections
// later on in the code...
Connection conn = ds.getConnection();
PreparedStatement stmt = null;
ResultSet rset = null;
String sql = "SELECT " + stepId + ", " + stepStepId + ", " + stepActionId + ", " + stepOwner + ", " + stepStartDate + ", " + stepDueDate + ", " + stepFinishDate + ", " + stepStatus + ", " + stepCaller + " FROM " + currentTable + " WHERE " + stepEntryId + " = ?";
stmt = conn.prepareStatement(sql);
stmt.setLong(1, entryId);
rset = stmt.executeQuery();
//close connections
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
默认情况下,您描述的行为应该是不可能的 - 在已提交的事务中所做的更改立即可供所有会话使用。 但是,也有例外:
您是否在 COMMIT 命令中使用任何 WRITE 选项? 如果不是,请确认 COMMIT_WRITE 初始化参数的值。 如果其中任何一个使用“WRITE BATCH”或特别是“WRITE BATCH NOWAIT”,您可能会遇到并发问题。 “WRITE BATCH NOWAIT”通常用于写入事务的速度比可能的并发问题更重要的情况。 如果您的初始化参数使用“WRITE”变体,则可以通过在提交中指定 IMMEDIATE 子句来在事务基础上覆盖它 (参见 COMMIT)
尝试读取数据的事务是否在其他事务提交之前调用 SET TRANSACTION? 使用 SET TRANSACTION 指定 SERIALIZATION LEVEL READ ONLY 或 SERIALIZABLE 将导致事务看不到调用 SET TRANSACTION 后发生的其他已提交会话中发生的任何更改 (参见 SET TRANSACTION)
编辑:我发现您正在使用 DataSource 类。 我不熟悉这个类 - 我认为它是一个连接共享资源。 我意识到您当前的应用程序设计可能无法在整个工作流程中轻松使用相同的连接对象(这些步骤可能设计为独立操作,并且您没有构建一种设施来将连接对象从一个步骤传递到另一个步骤) next),但您应该验证返回到 DataSource 对象的连接对象是否“干净”,特别是对于打开的事务。 您可能没有在代码中调用 SET TRANSACTION,但其他地方的 DataSource 的另一个使用者可能会这样做,并将连接返回到数据源,而会话仍处于 SERIALIZABLE 或 READ ONLY 模式。 当连接共享时,必须先回滚所有连接,然后再将它们移交给新的使用者。
如果您无法控制或查看 DataSource 类的行为,您可能希望尝试在新获取的连接上执行 ROLLBACK,以确保它没有已建立的延迟事务。
By default, the behavior you described should be impossible - changes made in a committed transaction become available immediately to all sessions. However, there are exceptions:
Are you using any of the WRITE options in the COMMIT command? If you are not, confirm the value of your COMMIT_WRITE initialization parameter. If either is using the "WRITE BATCH" or especially "WRITE BATCH NOWAIT", you could be opening yourself up to concurrency issues. "WRITE BATCH NOWAIT" would typically be used in cases where the speed of your write transactions is of greater importance than possible concurrency issues. If your initialization parameter is using the "WRITE" variants, you can override it on a transaction basis by specifying the IMMEDIATE clause in your commits (see COMMIT)
Is the transaction that is attempting to read the data invoking SET TRANSACTION prior to the other transaction committing? Using SET TRANSACTION to specify SERIALIZATION LEVEL READ ONLY or SERIALIZABLE will result in the the transaction seeing no changes that occur from other committed sessions that occurred after the invocation of SET TRANSACTION (see SET TRANSACTION)
edit: I see that you're using a DataSource class. I'm not familiar with this class - I assume it's a connection sharing resource. I realize that your current app design may not make it easy to use the same connection object throughout your work flow (the steps may designed to operate independently, and you didn't build in a facility to pass a connection object from one step to the next), but you should verify that connection objects being returned to the DataSource object are "clean", especially with regard to open transactions. It may be possible that you are not invoking SET TRANSACTION in your code, but another consumer of DataSource elsewhere may be doing so, and returning the connection back to the datasource with the session still in SERIALIZABLE or READ ONLY mode. When connection sharing, it is imperative that all connections be rolled back before handing them off to a new consumer.
If you have no control or visibility to the behavior of the DataSource class, you may wish to try executing a ROLLBACK on the newly acquired connection to insure it has no lingering transaction already established.
如果 DBA 团队尝试修改 max_commit_propagation_delay 参数,则可能意味着您正在连接到 RAC 实例(即:多个不同的服务器访问一个数据库)。
在这种情况下,当您在 java 代码中关闭并重新打开连接时,您可能会得到不同服务器的应答。 延迟参数意味着两个实例不会处于完全相同的时间点时有一个很小的时间范围。 您得到的答案与某个时间点一致,但可能不是最新的。
正如 KM 所建议的,最简单的解决方案是在提交后保持连接打开。
或者,如果可行的话,您也可以在关闭连接后添加延迟(例如,如果这是批处理作业并且响应时间并不重要)。
If the DBA team tried to modify the
max_commit_propagation_delay
parameter, it probably means you are connecting to a RAC instance (i-e: several distinct servers accessing one single database).In that case, when you're closing and reopening the connection in your java code there is a chance that you will be answered by a different server. The delay parameter means that there is a small time frame when the two instances won't be at exactly the same point in time. The answer you are getting is consistent with a point in time but may not be the most current.
As proposed by KM, the easiest solution would be to keep the connection opened after the commit.
Alternatively, you could also add a delay after having closed the connection if it is practical (if this is a batch job and response time is not critical for example).
正在使用 ORM? 它可能是从缓存中选择的,而不是在更改后形成数据库。
are use using an ORM? it might be selecting from cache and not form the db after the change.
这听起来像是 RAC 的问题,连接到两个不同的实例并且 SCN 不同步。
作为解决方法,请考虑不关闭数据库连接并获取新连接,而是重用同一连接。
如果这不起作用,则向尝试检索插入行的查询添加重试。 如果未返回该行,则休眠一下,然后再次重试查询。 将其放入循环中,在指定次数的重试后,您可能会失败。
[附录]
Steve Broberg (+1!) 在他的回答中提出了有趣的想法。 我没有考虑过:
COMMIT
可能是IMMEDIATE WAIT
之外的任何内容我确实考虑了闪回查询的可能性,并且立即驳回了这一点,但没有提及,因为没有明显的理由OP会使用闪回查询,并且代码片段中没有证据表明这样的事情。)
[/ADDENDUM]
This sounds like an issue with RAC, with connections to two different instances and the SCN is out of sync.
As a workaround, consider not closing the database connection and getting a new one, but reuse the same connection.
If that's not workable, then add a retry to the query that attempts to retrieve the inserted row. If the row is not returned, then sleep a bit, and retry the query again. Put that into a loop, after a specified number of retries, you can then fail.
[ADDENDUM]
In his answer, Steve Broberg (+1!) raises interesting ideas. I hadn't considered:
COMMIT
might be anything other thanIMMEDIATE WAIT
I did consider the possibility of flashback query, and dismissed that out of hand without mentioning it, as there's no apparent reason the OP would be using flashback query, and no evidence of such a thing in the code snippet.)
[/ADDENDUM]
一种可能的解决方法是使用 JTA 事务。
它通过多个打开/关闭 jdbc conns 使您的连接在“幕后”保持打开状态。 也许它会将您的连接保持在同一服务器上并避免此同步问题。
A possible workaround may be to use JTA transaction.
It keeps your connection open "behind the scene" over multiple open/close jdbc conns. Maybe it will keep your connection on the same server and avoid this sync' problem.
代码片段实际上并不包含提交。
如果您假设/依赖关闭连接来执行提交,则它可能不是同步的(即,当java告诉Oracle关闭连接时,java可能会报告连接已关闭,这意味着它可能是在提交完成之前)甲骨文)。
The code snippet didn't actually include the commit.
If you are assuming/relying on the close connection doing the commit, it may not be synchronous (ie the java may report the connection as closed when it tells Oracle to close the connection, which means it may be before the the commit is completed by Oracle).
我在你的代码中没有看到任何提交。 它们是此类应用程序中最重要的语句,因此我希望每次都明确地编写它们,而不是依赖于 close() 等。
您还可以在连接上默认将自动提交设置为 true,这将准确解释该行为(它在每次插入/更新后提交)。
您能否检查一下您是否已准确提交到您想要的位置,例如在事务结束时而不是之前?
如果在部分完成时有提交,那么线程之间就会出现竞争条件,这也可以解释为什么当负载较大时会出现更多问题。
I see no commit in your code. They are most important statements in such an app so I would want to have them explicitely written every time, not relying to close() or such.
You may also have autocommit set to true by default on your connection(s) which would exactly explain the behavior (it commits after every insert/update).
Can you check, that you have commits exactly where you want them, e.g. at the end of the transaction and not before?
If there are commits when you are partially through, then you have a race condition between your threads which would also explain why there are more problems when load is bigger.
“即使插入/更新提交在成功完成之前运行。”
这对我来说意味着您正在发出 commit(),然后期望再次读取完全相同的数据(这是可重复读取)。
这对我来说意味着你不应该做出承诺。 只要您想确保没有其他任务能够修改您明确希望保持稳定的任何数据,您就无法释放锁(这就是提交所做的)。
请注意,当您锁定某些资源时,其他线程将堆积起来“等待该资源变得可用”。 当您释放锁时,该堆栈非空的可能性随着一般系统负载的增加而增加。 当您(最终)发出“提交”时,您的 DBMS 将得出这样的结论:“嘿,哇,这个人终于完成了这个资源,所以现在我可以让所有其他等待的人尝试做他们的事情(并且没有什么可以阻止“他们的事情”更新!)”。
也许我忽略了与 Oracle 快照隔离有关的问题。 抱歉,如果是这样。
"even though the insert/update commit that ran before it completed successfully."
This suggests to me that you are issuing a commit(), and then afterwards expect to read exactly the same data again (that's repeatable read).
This suggests to me that you shouldn't be committing. As long as you want to make sure that NO OTHER TASK is able to modify ANY of the data that you EXPLICITLY EXPECT to remain stable, you cannot afford to release locks (which is what commit does).
Note that while you keep a lock on some resource, other threads will be stacking up "waiting for that resource to become available". The likelyhood of that stack being non-empty at the time you release your lock, gets higher as general system load gets higher. And what your DBMS will conclude when you (finally) issue "commit", is to conclude that, "hey, wow, this guy is finally done with this resource, so now I can go about letting all the other waiting guys try and do their thing with it (AND there is NOTHING to prevent "their thing" from being an update !)".
Maybe there are issues to do with Oracle's snapshot isolation that I'm overlooking. Apologies if so.