如何解决SQL合并与Java中的检查和插入/更新
我有一个Java(Spring)REST API端点,在其中我获得3个数据输入,并且需要使用ID
基于某些唯一的JDBCTemplate
插入oracle数据库。但是只是为了确保某些内容不会损坏,我想先检查一下是否需要插入或更新。
第一种方法
通过简单的查询进行数据库调用,例如
SELECT COUNT(*) FROM TABLENAME WHERE ID='ABC' AND ROWNUM=1
然后根据count的值,对插入或更新进行单独的数据库调用。 (计数永远不会超过1)
第二种方法
使用看起来像MERGE
的单个jdbctemplate.update()
查询命中
MERGE INTO TABLENAME
USING DUAL ON ID='ABC'
WHEN MATCHED THEN UPDATE
SET COL1='A',COL2='B'
WHERE ID='ABC'
WHEN NOT MATCHED THEN
INSERT (ID,COL1,COL2) VALUES ('ABC','A','B')
根据我在不同站点上所读到的内容,根据对this site进行的实验,使用MERGE
的CPU读取成本更高。但是他们只是为DB脚本使用而做的,他们使用2个表来做,而我的使用上下文是通过API调用并使用DUAL
。
我还读过on this question,发现MERGE
可能导致ORA-0001: unique constraint
和一些并发问题。
我想在一个表上执行此操作,在该表上可以同时对不同的行执行一些其他操作,而对于相同的行值则只有很小的机会。因此,我想知道这种用例应采用哪种方法,我知道这可能是一种常见的方法,但是我找不到任何地方都可以找到答案。我想知道两种方法的性能/可靠性。
解决方法
看看在并发会话环境中运行的代码,在每个原子语句之后,我们需要问“另一个会话是否刚刚打破了我们的假设?”并据此进行调整。
选项1。计算并决定INSERT或UPDATE
declare
v_count int;
begin
SELECT count(1) INTO v_count FROM my_table WHERE ...;
IF v_count = 0 THEN
-- what if another session inserted the same row just before this point?
-- this statement will fail
INSERT INTO my_table ...;
ELSE
UPDATE my_table ...;
END IF;
end;
选项2。更新,如果没有更新-插入
begin
UPDATE my_table WHERE ...;
IF SQL%COUNT = 0 THEN
-- what if another session inserted the same row just before this point?
-- this statement will fail
INSERT INTO my_table ...;
END IF;
end;
选项3 。如果失败-UPDATE
begin
INSERT INTO my_table ...;
exception when DUP_VAL_ON_INDEX then
-- what if another session updated the same row just before this point?
-- this statement will override previous changes
-- what if another session deleted this row?
-- this statement will do nothing silently - is it satisfactory?
-- what if another session locked this row for update?
-- this statement will fail
UPDATE my_table WHERE ...;
end;
选项4。使用MERGE
MERGE INTO my_table
WHEN MATCHED THEN UPDATE ...
WHEN NOT MATCHED THEN INSERT ...
-- We have no place to put our "what if" question,-- but unfortunately MERGE is not atomic,-- it is just a syntactic sugar for the option #1
选项5。在my_table
上使用DML界面
-- Create single point of modifications for my_table and prevent direct DML.
-- For instance,if client has no direct access to my_table,-- use locks to guarantee that only one session at a time
-- can INSERT/UPDATE/DELETE a particular table row.
-- This could be achieved with a stored procedure or a view "INSTEAD OF" trigger.
-- Client has access to the interface only (view and procedures),-- but the table is hidden.
my_table_v -- VIEW AS SELECT * FROM my_table
my_table_ins_or_upd_proc -- PROCEDURE (...) BEGIN ...DML on my_table ... END;
PROCEDURE my_table_ins_or_upd_proc(pi_row my_table%ROWTYPE) is
l_lock_handle CONSTANT VARCHAR2(100) := 'my_table_' || pi_row.id;
-- independent lock handle for each id allows
-- operating on different ids in parallel
begin
begin
request_lock(l_lock_handle);
-->> this code is exactly as in option #2
UPDATE my_table WHERE ...;
IF SQL%COUNT = 0 THEN
-- what if another session inserted the same row just before this point?
-- NOPE it cannot happen: another session is waiting for a lock on the line # request_lock(...)
INSERT INTO my_table ...;
END IF;
--<<
exception when others then
release_lock(l_lock_handle);
raise;
end;
release_lock(l_lock_handle);
end;
在这里不深入介绍低级细节,请参阅this article以了解如何在Oracle DBMS中使用锁。
因此,我们看到选项1,2,3,4存在潜在的问题,在一般情况下无法避免。但是如果域规则或特定的设计约定可以保证安全性,则可以应用它们。
选项5依赖DBMS合同,因此防弹快速。
但是,这将是清洁设计的奖励,如果my_table
被讨价还价并且客户依赖此表上简单的DML,则无法实现。
我相信性能并不比数据完整性重要,但是为了完整起见,我们要提到它。 经过适当考虑后,很容易看到根据“理论”平均表现的期权顺序为:
2 -> 5 -> (1,4) -> 3
当然,性能度量的步骤是在获得至少两个正常工作的解决方案之后进行的,并且应该在给定的工作负载配置文件下专门针对特定应用程序执行。那是另一个故事。目前,在某些合成基准测试中无需理会理论纳秒。
我想目前我们看到不会有魔法。在应用程序中的某个位置,需要确保插入到id
中的每个my_table
是唯一的。
如果id值无关紧要(95%的情况)-只需使用SEQUENCE。
否则,请在my_table
(在Java或DBMS模式PL / SQL中)上创建一个单点操作,并在那里控制唯一性。如果应用程序可以保证一次最多只有一个会话来处理my_table
中的数据,则可以仅应用选项#2。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。