我在一个WebLogic群集中运行了几个J2EE应用程序实例。
在某些时候,这些应用程序会执行MERGE以将记录插入或更新到后端Oracle数据库中。 MERGE检查是否存在具有指定主键的行。如果它在那里,请更新。如果没有,请插入。
现在假设两个应用实例想要插入或更新主键= 100的行。假设该行不存在。在合并的“检查”阶段,他们都看到行不在那里,所以他们都试图插入。然后我得到一个唯一的密钥约束违规。
我的问题是:Oracle中是否存在原子MERGE?我正在寻找与PL / SQL中的INSERT ... FOR UPDATE
具有类似效果的东西,除了我只能从我的应用程序执行SQL。
答案 0 :(得分:15)
这对MERGE来说不是问题。相反,问题在于您的应用程序。考虑这个存储过程:
create or replace procedure upsert_t23
( p_id in t23.id%type
, p_name in t23.name%type )
is
cursor c is
select null
from t23
where id = p_id;
dummy varchar2(1);
begin
open c;
fetch c into dummy;
if c%notfound then
insert into t23
values (p_id, p_name);
else
update t23
set name = p_name
where id = p_id;
end if;
end;
因此,这是T23上MERGE的PL / SQL等价物。如果两个会话同时调用它会发生什么?
SSN1> exec upsert_t23(100, 'FOX IN SOCKS')
SSN2> exec upsert_t23(100, 'MR KNOX')
SSN1首先到达那里,找不到匹配的记录并插入记录。 SSN2到达第二,但在SSN1提交之前,找不到记录,插入记录并挂起,因为SSN1在100的唯一索引节点上有一个锁定。当SSN1提交SSN2时将抛出DUP_VAL_ON_INDEX违规。
MERGE语句以完全相同的方式工作。两个会话都将检查on (t23.id = 100)
,找不到它并进入INSERT分支。第一个会议将成功,第二个会议将推出ORA-00001。
处理此问题的一种方法是引入悲观锁定。在UPSERT_T23程序开始时,我们锁定表格:
...
lock table t23 in row shared mode nowait;
open c;
...
现在,SSN1到达,抓住锁并继续前进。当SSN2到达时它无法获得锁定,因此它立即失败。这对于第二个用户来说是令人沮丧的,但至少他们没有挂起,而且他们知道其他人正在处理相同的记录。
INSERT没有语法等同于SELECT ... FOR UPDATE,因为没有什么可以选择的。所以MERGE也没有这样的语法。您需要做的是在发出MERGE的程序单元中包含LOCK TABLE语句。这是否可行取决于您使用的框架。
答案 1 :(得分:6)
第二个会话中的MERGE语句不能“看到”第一个会话在该会话提交之前所执行的插入。如果减少交易的大小,那么这种情况发生的可能性就会降低。
或者,您可以对数据进行排序或分区,以便将给定主键的所有记录都提供给同一会话。像“主键mod N”这样的简单函数应该均匀分配给N个会话。
顺便说一句,如果两个记录具有相同的主键,则第二个记录将覆盖第一个。听起来有点奇怪。
答案 2 :(得分:3)
是的,它被称为.... MERGE
编辑:唯一可以解决此问题的方法是插入,捕获dup_val_on_index异常并对其进行适当处理(更新或插入其他记录)。这可以通过PL / SQL轻松完成,但不能使用它。
您还在寻找解决方法。你能在Java中捕获dup_val_on_index并再次发出额外的UPDATE吗?
在伪代码中:
try {
// MERGE
}
catch (dup_val_on_index) {
// UPDATE
}
答案 3 :(得分:2)
我很惊讶MERGE会按照你描述的方式行事,但我没有充分利用它来说明它是否应该。
在任何情况下,您可能希望执行合并的事务将其隔离级别设置为SERIALIZABLE。我认为这可以解决您的问题。