【问题标题】:SQL Merge vs Check and Insert/Update in JavaJava中的SQL合并与检查和插入/更新
【发布时间】:2020-12-04 06:33:44
【问题描述】:

我有一个 Java(Spring) REST API 端点,我在其中获得 3 个数据输入,我需要使用 JDBCTemplate 基于一些独特的 ID 在 Oracle 数据库中插入。但为了确保不会出现问题,我想先检查一下是否需要插入或更新。

第一种方法

用一个简单的查询来调用数据库

 SELECT COUNT(*) FROM TABLENAME WHERE ID='ABC' AND ROWNUM=1

并根据 count 的值,对 Insert 或 Update 进行单独的数据库调用。 (计数永远不会超过 1)

第二种方法

使用jdbctemplate.update() 进行单个MERGE 查询命中,看起来像

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 questionMERGE 可能导致ORA-0001: unique constraint 和一些并发问题。

我想在一个表上执行此操作,在该表上可以同时对不同的行进行一些其他操作,而对同一行值的可能性非常小。所以我想知道这种用例应该遵循哪种方法,我知道这可能是一种常见的方法,但我无法在任何地方找到我正在寻找的答案。我想知道这两种方法的性能/可靠性。

【问题讨论】:

  • 您的选项 3 和 4 是:3) 始终先更新,如果没有更新行 -> 插入。 4)总是先插入,如果重复行失败->更新。当然,一切都在很大程度上取决于您拥有什么样的更新/插入模式。如果调用的数量不是那么大,就做你觉得最简单的事情。

标签: java sql spring oracle performance


【解决方案1】:

查看在并发会话环境中运行的代码,在每个原子语句之后,我们需要问“如果另一个会话刚刚打破了我们的假设怎么办?”并据此进行调整。

选项 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. 插入,如果失败 - 更新

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

当然,性能测量步骤是在获得至少两个正常工作的解决方案之后进行的,并且应该专门针对给定工作负载配置文件下的特定应用程序进行。那是另一个故事。目前无需在一些综合基准测试中关心理论纳秒。


我想目前我们看到不会有魔法。在应用程序的某个地方,需要确保插入到my_table 中的每个id 都是唯一的。

如果 id 值无关紧要(95% 的情况) - 只需使用 SEQUENCE

否则,在my_table(在 Java 或 DBMS 模式 PL/SQL 中)上创建单点操作并控制那里的唯一性。如果应用程序可以保证一次最多一个会话处理my_table 中的数据,则可以只应用选项#2。

【讨论】:

  • 谢谢。选项 #3 最适合我,因为它不会面临您提到的挑战,并且提供此端点仅用于更新。更新检查只是为了确保没有中断。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-14
  • 1970-01-01
  • 2016-12-04
相关资源
最近更新 更多