【问题标题】:Concurrent update and delete on one table在一张表上同时更新和删除
【发布时间】:2017-04-03 18:46:09
【问题描述】:

使用 Java、Hibernate 和 Oracle 数据库。 我有两个并发进程:

  1. Process1 从 table1 中删除一些实体。 (multiple: delete from table1 where id =...) 由本地休眠查询完成。
  2. Process2 更新 table1 中的 SAME/其他实体。 (multiple: update table1 set name=... where id=...) 由 jpa repository delete 方法完成。

目前有时异常

抛出CannotAcquireLockException,
(SQL 错误:60,SQLState:61000..
ORA-00060: 等待资源时检测到死锁)

所以,问题是:发生了什么以及如何避免异常?有什么解决方法吗?

重要提示:如果发生冲突,如果删除成功并且更新不会做任何事情,我会很满意。

【问题讨论】:

  • 您不认为minimal reproducible example 会有所帮助吗?如果您查看从死锁生成的 oracle 跟踪文件,它将为您提供有关导致问题的所有详细信息。
  • 我认为我对问题的描述已经足够好了。如果我有一个 Java 代码可以在任何 oracle 数据库上 100% 出现此错误,我肯定会提供给您。

标签: java oracle hibernate


【解决方案1】:

会话 A 等待 B,B 等待 A - 这基本上就是死锁。 无需再等待,Oracle 将终止其中一个会话。

选项 1

创建信号量以有效地序列化并发进程。

create table my_semaphore(dummy char(1));

第 1 节:

LOCK TABLE my_semaphore in exclusive mode;
UPDATE <your update here>;
COMMIT;

第 2 节:

LOCK TABLE my_semaphore in exclusive mode;
DELETE <your delete here>;
COMMIT;

选项 2 尝试以相同的顺序处理两个语句的行,比如按 rowid 或其他。

因此,如果 A 被 B 锁定的行卡在后面,那么会话 B 永远不会返回到 A 持有的行。这更加棘手和资源密集。

【讨论】:

  • 锁定表看起来一点也不吸引人 - 那么让多个进程与数据库一起工作的意义何在
  • 你是对的。这就是为什么我建议锁定一个单独的表,而不是带有实际数据的表,以便仅有效地序列化两个麻烦的事务。
  • 明天试试第二个
【解决方案2】:

“锁定表看起来一点也不吸引人——那么让多个进程与数据库一起工作有什么意义”

显然我们想要启用并发进程。诀窍是设计可以同时运行而不会相互干扰的进程。您的架构未能解决这一点。进程 B 应该不可能更新进程 A 正在删除的记录。

这是整个 web 范式的一个不幸的副作用,它是无状态的并且有利于乐观锁定策略。在最后可能的时刻获得锁会“扩展”但会带来死锁的风险。

另一种选择是悲观锁定策略,其中会话预先锁定它想要的行。在 Oracle 中,我们可以使用 SELECT .. FOR UPDATE 来做到这一点。这会锁定行的子集(由 WHERE 子句定义的集合)而不是整个表。 Find out more.

因此,它不会阻碍对不同数据子集进行操作的并发进程,但会阻止第二个会话抓取已被处理的记录。这仍然会导致第二个会话出现异常,但至少会在会话完成任何工作之前发生,并提供重新评估任务的信息(嗯,如果它们正在更新,我们是否要删除这些记录?) .

Hibernate 支持 SELECT FOR UPDATE 语法。 This StackOverflow thread discusses it.

【讨论】: