【问题标题】:Optimized way to select and update large tables选择和更新大表的优化方式
【发布时间】:2013-11-15 23:10:45
【问题描述】:

我必须通过加入一组大表来选择一组值,然后从选定的值更新另一个大表。我目前遵循以下方法。但我看到了性能缺陷。有哪些替代方法可以完成上述工作?

数据库服务器:Oracle EE

DECLARE
  CURSOR c1
  IS
    SELECT update_data FOR UPDATE OF abc;
BEGIN
  FOR update_data IN c1
  LOOP
    UPDATE klm
    SET klm.xyz  = update_data.cdf
    WHERE update_data.abc = klm.abc;
  END LOOP;
  COMMIT;
END; 

【问题讨论】:

    标签: sql oracle plsql oracle11g query-performance


    【解决方案1】:

    很可能简单的更新会执行得更好。

    你可以试试:

    update klm t1
    set xyz = ( select cdf from update_data t2 where t2.abc = t1.abc ) 
    where exists ( select 1 from update_data t2 where t2.abc = t2.abc );
    
    commit;
    

    或者如果可能的话(在 update_data.abc 上有 PK 或唯一索引)

    update ( select t1.xyz, t2.cdf from klm t1, update_data t2 where t1.abc = t2.abc ) 
    ) set xyz = cdf; 
    
    commit;
    

    【讨论】:

    • 感谢您的回答。我尝试了第二种方法,但出现错误 SQL 错误:ORA-01779:无法修改映射到非键保留表的列
    • 而且我也无法理解第一个解决方案的存在部分。为什么要检查 t2.abc = t2.abc ?谢谢
    • 与第一个答案有关; update_data 又是一个生成的。我认为执行 update_data SELECT 查询两次(在 SET 部分和 WHERE EXIST 中)不是最佳选择。谢谢
    • 当我尝试第一个解决方案时,我得到了 ORA-01427:单行子查询返回多行。 update_data 返回一千行要更新的行。谢谢
    • 在 SET 中选择应该只返回一行 - 您只能从 完全 一行更改值。
    【解决方案2】:

    如果您在遍历每条记录时遇到性能问题,但表太大而无法进行单次更新,您可以考虑使用 BULK INTO ... LIMIT 和 FORALL 进行批量更新。

    CREATE TABLE klm (abc INTEGER, xyz INTEGER);
    CREATE TABLE update_data (abc INTEGER, cdf INTEGER);
    
    -- Have pairs of numbers (1000 rows)
    INSERT INTO klm SELECT rownum, rownum FROM dual CONNECT BY level <= 1000;
    -- Update every second row with 9999
    INSERT INTO update_data SELECT rownum * 2, 9999 FROM dual CONNECT BY level <= 500;
    
    DECLARE
      CURSOR c1
      IS
        -- Select the key to be updated and the new value
        SELECT abc, cdf FROM update_data;
      -- Table type and table variable to store rows fetched from the cursor
      TYPE t_update IS TABLE OF c1%rowtype;
      update_tab t_update;
    BEGIN
      OPEN c1;
      LOOP
        -- Fetch next 30 rows into update table
        FETCH c1 BULK COLLECT INTO update_tab LIMIT 30;
        -- Exit when there were no more rows fetched
        EXIT WHEN update_tab.count = 0;
        -- This is the key point; uses update_tab to bulk-bind UPDATE statement
        -- and run it for 30 rows in a single context switch
        FORALL i IN 1..update_tab.count
          UPDATE klm
          SET klm.xyz  = update_tab(i).cdf
          WHERE update_tab(i).abc = klm.abc;
        COMMIT;
      END LOOP;
      CLOSE c1;
    END;
    /
    

    这背后的基本原理是,Oracle 实际上有单独的引擎运行 SQL 语句和 PL/SQL 程序。每当一个过程遇到一条 SQL 语句时,它就会将它交给 SQL 引擎执行。这称为“上下文切换”,需要花费大量时间,尤其是在循环中完成时。

    批量绑定旨在通过对每个 [批量大小] 记录进行一次上下文切换来减少这种开销。同样,这肯定不如单个 DML 操作有效,但对于大型表或复杂查询,它可能是最佳可行的解决方案。

    我已使用上述方法更新具有 100M-500M 记录的表,批量大小为 10K-100K,并且效果很好。但是您需要在您的环境中试验批量大小以获得最佳性能。

    【讨论】:

    • 感谢 Kombajn。我可以理解循环。但不清除选择部分。你能解释一下选择逻辑吗?
    • SELECT 语句只需从 update_data 表中选择键 (abc) 和新值 (cdf),如果这就是您的意思。无论如何,我添加了一些cmets,我希望现在会更清楚。也看看Oracle Base artice on bulk-bind
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-11
    相关资源
    最近更新 更多