并行事务中执行的Oracle MERGE语句:如何防止重复键?

时间:2016-07-07 12:40:41

标签: sql oracle

让我们想象一下,我们有一些漂亮,简单的表格:

CREATE TABLE blah (
    some_key VARCHAR(32),
    some_value VARCHAR(32)
);

出于某种原因,我们不希望在some_key列上定义主键或唯一索引。

我们现在在并行事务中执行MERGE语句(即第二个语句在第一个提交之前启动):

MERGE INTO blah blah
USING (SELECT 'some_key' some_key, 'a_value' some_value FROM DUAL) rec
ON (blah.some_key = rec.some_key)
WHEN MATCHED THEN
UPDATE SET blah.some_value = rec.some_value
WHEN NOT MATCHED THEN
INSERT (blah.some_key, blah.some_value) VALUES (rec.some_key, rec.some_value);

...
USING (SELECT 'some_key' some_key, 'another_value' some_value FROM DUAL) rec
...

提交后,SELECT显示以下内容:

| SOME_KEY | SOME_VALUE    | 
|----------|---------------|
| some_key | a_value       |
| some_key | another_value |

我理解这背后的机制(即两个合并声明不是"看到"彼此),但这显然不是理想的结果。

当然,我可以定义一个UNIQUE密钥。然后第二个问题失败了 - 这也是可以解释的,但在MERGE声明中有些意外。

有没有办法让MERGE做一个真实的" MERGE没有任何重复"键"?

1 个答案:

答案 0 :(得分:2)

如果在some_key上创建唯一约束是有意义的,那么你真的应该创建它。这是完全防止重复密钥的唯一方法。

除了创建唯一约束之外,听起来,你真正想要的是能够序列化合并语句。换句话说,为了保证2个并发用户永远不能同时真正运行两个merge语句。相反,必须等到另一个完成后,没有其中一个用户的麻烦得到错误。

这是一个想法,实现不涉及锁定您正在合并的整个表。您可以创建一个专门用于序列化整个操作的单独表。然后,您可以使用select ... for update锁定该表的特定行,以表示您的操作。实际上,这会序列化对merge语句的访问。然后,在执行merge之后,您提交事务以允许其他人执行merge,但只有在更改对其他事务可见之后才会执行。

以下示例脚本为您提供了一个想法:

<强>设置

create table operation_lock (
  operation_name varchar2(50) not null
)
/

alter table operation_lock
add constraint operation_lock_pk
primary key (operation_name)
/

insert into operation_lock (operation_name) values ('my_merge_operation')
/

commit
/

如何在交易中运行merge操作

select * from operation_lock
 where operation_name = 'my_merge_operation'
   for update
/

merge into blah blah...
/

commit
/