我可以在 Oracle 中进行原子合并吗?

Posted

技术标签:

【中文标题】我可以在 Oracle 中进行原子合并吗?【英文标题】:Can I do an atomic MERGE in Oracle? 【发布时间】:2011-05-12 17:27:21 【问题描述】:

我有几个在单个 WebLogic 集群中运行的 J2EE 应用程序实例。

在某些时候,这些应用会执行 MERGE 以将记录插入或更新到后端 Oracle 数据库中。 MERGE 检查是否存在具有指定主键的行。如果它在那里,请更新。如果没有,请插入。

现在假设两个应用实例想要插入或更新主键 = 100 的行。假设该行不存在。在合并的“检查”阶段,他们都看到行不存在,所以他们都尝试插入。然后我得到一个唯一键约束违规。

我的问题是:Oracle 中是否有原子 MERGE?我正在寻找与 PL/SQL 中的INSERT ... FOR UPDATE 具有类似效果的东西,只是我只能从我的应用程序中执行 SQL。

编辑:我不清楚。我正在使用 MERGE 语句,而此错误仍然发生。问题是,只有“修改”部分是原子的,而不是整个合并。

【问题讨论】:

合并是原子的。它作为一个完整的工作单元工作或失败。您所看到的是 Oracle 实施多版本一致性的结果。听起来您正在寻找一些东西来序列化多个合并?您可以尝试使用 Oracle 的可序列化事务,但这可能只会将错误从唯一键约束更改为无法序列化事务错误。 我的数据库/多线程词汇可能有误。我的理解是“作为一个完整的工作单元工作或失败”被称为事务性的。通过原子,我的意思是在另一个合并正在处理时不会发生合并。关于序列化合并,我将不得不阅读相关内容。谢谢。 @Russell,原子性只是事务的一个属性。它确实意味着作为一个整体的成功或失败。例如,请参阅:en.wikipedia.org/wiki/ACID 和 download.oracle.com/docs/cd/E11882_01/server.112/e16508/…。 主键从哪里来,你可以有新的重复? 啊,对 ACID 的精彩回顾。谢谢。自从我上次使用数据库以来,我很久以前就没有听说过。主键来自我无法控制的外部来源。 【参考方案1】:

这不是 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 违规。 p>

MERGE 语句的工作方式完全相同。两个会话都会检查on (t23.id = 100),没有找到它并进入INSERT 分支。第一个会话将成功,第二个会话将抛出 ORA-00001。

解决此问题的一种方法是引入悲观锁定。在 UPSERT_T23 过程开始时,我们锁定表:

...
lock table t23 in row shared mode nowait;
open c;
...

现在,SSN1 到达,抓住锁并像以前一样继续。当 SSN2 到达时它无法获得锁,因此它立即失败。这对第二个用户来说是令人沮丧的,但至少他们没有挂起,而且他们知道其他人正在处理相同的记录。

没有与 SELECT ... FOR UPDATE 等效的 INSERT 语法,因为没有可供选择的内容。因此 MERGE 也没有这样的语法。您需要做的是在发出 MERGE 的程序单元中包含 LOCK TABLE 语句。这对您来说是否可行取决于您使用的框架。

【讨论】:

谢谢,这完美地表达了我在我的问题中试图解释的内容。关于你将如何处理这个问题,锁不是 PL/SQL 的一部分吗?我正在寻找可以从我的应用程序调用的使用 Oracle 的 SQL 的解决方法/解决方案。我并不是真的在寻找相当于 SELECT ... FOR UPDATE 的 INSERT (尽管我所说的方式可能会让你这么想)。我只是在寻找是否有任何方法可以通过 MERGE 语句锁定正在合并的行(因此是问题的标题)。无论如何,感谢您的详尽回答和知识:) 锁定行共享(不是行共享)中的表几乎没有任何作用。它将阻止另一个会话获得独占表锁。它不会阻止另一个会话更新/插入/删除。顺便说一句,当您发出“select .. for update”语句时,会自动获取行共享锁。 “共享行独占”模式将阻止其他会话更改表,如果另一个会话有未提交的更改,您将无法获取它。这几乎与独占锁一样具有限制性。使用它将阻止所有并发更新 - 就像“单用户”系统。【参考方案2】:

第二个会话中的 MERGE 语句在该会话提交之前无法“看到”第一个会话所做的插入。如果您减少事务的大小,那么发生这种情况的可能性就会降低。

或者,您能否对数据进行排序或分区,以便将给定主键的所有记录提供给同一会话。像“primary key mod N”这样的简单函数应该平均分配给 N 个会话。

顺便说一句,如果两条记录具有相同的主键,第二条将覆盖第一条。听起来有点奇怪。

【讨论】:

这没有回答问题。如果您想同步两个只有 db 作为公共元素的单独应用程序,则该解决方法将不起作用。【参考方案3】:

是的,它被称为....MERGE

编辑:获得这种水密性的唯一方法是插入、捕获 dup_val_on_index 异常并适当地处理它(更新或插入其他记录)。这可以通过 PL/SQL 轻松完成,但您不能使用它。

您也在寻找解决方法。你能在 Java 中捕获 dup_val_on_index 并再次发出额外的 UPDATE 吗?

在伪代码中:

try 
  // MERGE

catch (dup_val_on_index) 
  // UPDATE

【讨论】:

好的,周末后我会再研究一下! 你会使用 PL/SQL 吗?我发现的第一件事表明带有异常处理的插入(当 dup_val_on_index 然后更新时)是使这种水密的唯一方法。 如问题所述,不,我不能。我也在考虑那个选项。 在使用JPA时,到达Java中的catch部分意味着事务已经被标记为回滚,那么就没有机会提交更新了! @MartinSchapendonk - 捕获了什么样的异常? SQLException?【参考方案4】:

我很惊讶 MERGE 会按照您描述的方式运行,但我没有充分使用它来说明它是否应该这样做。

在任何情况下,您都可能让希望执行合并的事务将其隔离级别设置为 SERIALIZABLE。我认为这可能会解决您的问题。

【讨论】:

以上是关于我可以在 Oracle 中进行原子合并吗?的主要内容,如果未能解决你的问题,请参考以下文章

我可以在 GitHub 中查看合并功能分支的所有提交吗?

Neo4j 合并和原子事务

如何根据不同的列合并行(可以查询吗?)

我们可以提交已经与 master 合并的分支吗?

整行不匹配时的oracle合并语句[重复]

专业技术人员档案可以和人事档案合并吗?