如何从SELECT性能改进大量UPDATE?

时间:2016-09-15 20:37:05

标签: sql oracle

我想用另一个表中的数据更新表。我目前的方法如下:

UPDATE items t1
SET (name, manufacturer_id, price) =
    (SELECT
        t2.item_name,
        t2.item_manufacturer_id,
        t2.item_price
    FROM staged_items t2
    WHERE t2.upgrade_version = 1234
      AND t2.operation = 'modification'
      AND t1.id = t2.item_id)
WHERE EXISTS (
SELECT 1
FROM staged_items t2
WHERE t2.upgrade_version = 1234
  AND t2.operation = 'modification'
  AND t1.id = t2.item_id)

问题在于它花了太长时间,我不知道如何获得反馈或估计需要多长时间。我一直在等待大约3个小时没有结果,并且v$session_longops上没有待处理的操作。

我想知道如何提高更新的性能,或者另一种更有效的方法。此外,我想知道如何检查正在运行的查询的状态


一些评论

  • staged_items表预计至少包含 3亿条目,并且从长远来看会增长到数十亿条目。

    • 每次运行t2.upgrade_version = somenumber时,预计只有“少数条目”(从0到100万)与条件(t2.operation = 'modification'UPDATE)匹配。
    • 目前我正在测试一个包含100万个条目的staged_items个表,这些表都符合条件。
  • items表预计会有大约 2000万个参赛作品,并且从长远来看会保持这个数量级。

    • 目前我正在测试大约约100万个条目,其中大部分都与UPDATE匹配。
  • 目前我没有索引,但我正考虑在items.idstaged_items.item_idstaged_items.upgrade_versionstaged_items.operation

    上创建索引
    • 我不太确定哪些索引确实需要和有用
  • 预计UPDATE不会修改任何索引列,无论如何这可能会在将来发生变化,因此我会评论该场景将如何对建议的解决方案产生影响。


编辑最后,我选择使用rownum将查询分页到多个查询,而不是检查正在运行的查询的状态。这允许我等待每个(较小的)查询并检查并估计完整性的百分比。

考虑到这一点,我的原始查询看起来像这样:

UPDATE items t1
SET (name, manufacturer_id, price) =
    (
      SELECT
        t2.item_name,
        t2.item_manufacturer_id,
        t2.item_price
      FROM
        (
          SELECT /*+ FIRST_ROWS(n) */ 
            a.*,
            ROWNUM rnum
          FROM
            (
              SELECT *          
              FROM staged_items t2
              WHERE t2.upgrade_version = 1234
                AND t2.operation = 'modification'
                AND t1.id = t2.item_id
              ORDER BY t2.id
            ) a
          WHERE ROWNUM <= MAX_ROW_TO_FETCH
        )
      WHERE rnum >= :MIN_ROW_TO_FETCH
    )
WHERE EXISTS (
SELECT 1
FROM staged_items t2
WHERE t2.upgrade_version = 1234
  AND t2.operation = 'modification'
  AND t1.id = t2.item_id)

(基于this link对ROWNUM的分页部分)

无论如何,对于外WHERE,我使用了Gordon Linoff's solution

4 个答案:

答案 0 :(得分:2)

对于您的查询,您需要一个索引:

UICollectionView

我也在考虑你可以将外部staged_items(item_id, upgrade_version, operation) 子句重写为:

where

然后,您需要WHERE t1.id IN (SELECT t2.item_id FROM staged_items t2 WHERE t2.upgrade_version = 1234 AND t2.operation = 'modification' ) staged_items(upgrade_version, operation, item_id)上的索引。请注意,索引中键的顺序很重要,您仍然希望相关子查询的第一个索引获取值。

答案 1 :(得分:1)

对于一个走进医生办公室的男人来说,这是一个老笑话。他挥动他的手臂,然后说,#Doc;当我这样做时,它很疼!&#34;。博士看着他,然后说,“那么,那就不要那样做了!&#34;

我认为你的主要问题是你的临时表很大,但你真的只需要查看一小部分数据。不要使用完整的临时表进行更新。也许您可以尝试创建在运行更新之前刷新完成的物化视图。您的席子视图将基于:

SELECT
        t2.item_name,
        t2.item_manufacturer_id,
        t2.item_price
    FROM staged_items t2
    WHERE t2.upgrade_version = 1234
      AND t2.operation = 'modification'

您也可以为此添加并行提示。如果每次运行更新时都需要更改这些值,也可以通过CTAS创建常规表(create table as select),使用相同的SQL但具有不同的值(upgrade_version = 5678或其他)。

您的另一个问题是跟踪。最干净的方法是在pl / sql中。它可能不像单个更新语句那么简单,但您可以添加日志记录并控制您的提交点(您的DBA会对此表示感谢)。

您的驾驶表将是垫视图(或CTAS表)。类似的东西:

declare
  cursor sel_stage_mv is
  select * from my_stage_mv;

  l_cnt pls_integer := 0;
  l_upd_cnt pls_integer := 0;
begin
  for rec in sel_stage_mv
  loop
    l_cnt := l_cnt + 1;

    -- all needed indexes are on main table (id, etc...)
    update main_table
    set ...
    where id = rec.id;

    l_upd_cnt := l_upd_cnt + SQL%ROWCOUNT;

    if (mod(l_cnt, 10000) = 0) then
      -- insert to some log table via autonomous procedure
      ins_log(...l_upd_cnt ...);
      commit;
    end if;

  end loop;
  commit;
end;

当然,所有这些都要运行。我还对您的环境和交易要求做了一些假设,但只有您知道什么对您的设置和需求有用。

答案 2 :(得分:0)

如果您需要在表格中执行大量DML操作,则可以使用BULK COLLECT和FORALL。为了获得操作状态,我通常会创建一个日志表来存储信息。 而且,最重要的是,您可以添加一些索引来加速查询。

我不会使用MERGE语句,因为它非常慢。

所以,一个可能的解决方案是这样的:

create table log_load(table_name varchar2(50), create_date date, message varchar2(500));

declare
cursor cur_upd is
SELECT rowid as row_id
FROM items t1
WHERE EXISTS (
  SELECT 1
  FROM staged_items t2
  WHERE t2.upgrade_version = 1234
  AND t2.operation = 'modification'
  AND t1.id = t2.item_id);

TYPE fetch_array IS TABLE OF cur_upd%ROWTYPE;
s_array fetch_array;
BEGIN
    insert into log_load values ('item',sysdate,'Start')
    commit;

    OPEN cur_upd;
    upd_ := 0;
    LOOP
         FETCH cur_upd BULK COLLECT INTO s_array LIMIT 50000;
         upd_ := upd_ + s_array.COUNT;

        FORALL i IN 1..s_array.COUNT
         UPDATE items t1
         SET (name, manufacturer_id, price) =
          (SELECT t2.item_name, t2.item_manufacturer_id, t2.item_price
           FROM staged_items t2
           WHERE t2.upgrade_version = 1234
           AND t2.operation = 'modification'
           AND t1.id = t2.item_id)
         WHERE t1.rowid = cur_upd[i].row_id;

    insert into log_load values ('item',sysdate,'50000 updated.')
    commit;

    EXIT WHEN cur_upd%NOTFOUND;
   END LOOP;

   CLOSE cur_upd;
   insert into log_load values ('item',sysdate,'end');
   commit;

END;

答案 3 :(得分:0)

也许试试这个:

UPDATE 
(SELECT t1.name, t1.manufacturer_id, t1.price,
   t2.item_name,
   t2.item_manufacturer_id,
   t2.item_price, 
   t1.id, t2.item_id
from items t1
   JOIN staged_items t2 on t1.id = t2.item_id
WHERE t2.upgrade_version = 1234
   AND t2.operation = 'modification')
SET name = item_name, 
   manufacturer_id = item_manufacturer_id, 
   price = item_price;

它应该避免staged_items的双重SELECT,因此可以更快。 但是,我没有测试它。