【问题标题】:Update / EXISTS or MERGE for better performance? Slow updating on big table (millions of records)更新/存在或合并以获得更好的性能?大表更新缓慢(数百万条记录)
【发布时间】:2020-05-30 13:51:09
【问题描述】:

我需要一些关于性能问题的帮助:

我有一个包含 2700 万条记录的表 CONTRATOS。此表有多个列,但重要的是:CONTRATO、CODIGO_ORIGEN。

每个 CODIGO_ORIGEN 可以存在多个 CONTRATO,所以我需要这样做:

SELECT DISTINCT 
    CODIGO_ORIGEN,
    FIRST_VALUE(CONTRATO) OVER (PARTITION BY CODIGO_ORIGEN ORDER BY FECHA DESC) MAX_CONTRATO
FROM TABLE CONTRATOS;

在这之后,我们有 600 万条记录,好的记录...

我需要做的是用 ESTADO 列上的值“E”更新表 CONTRATOS 中的“未找到”记录 (CONTRATO)。

起初,我尝试一步完成所有事情(查询):

UPDATE / NOT EXISTS METHOD
--------------------------

    UPDATE CONTRATOS C
    SET ESTADO = 'E'
    WHERE NOT EXISTS
    (
        SELECT 1
        FROM
        (
            SELECT DISTINCT 
                CODIGO_ORIGEN,
                FIRST_VALUE(CONTRATO) OVER (PARTITION BY CODIGO_ORIGEN ORDER BY FECHA DESC) MAX_CONTRATO
            FROM TABLE CONTRATOS
        ) C2
        WHERE C.CONTRATO = C2.MAX_CONTRATO
    );

MERGE METHOD
---------------

    MERGE INTO CONTRATOS C
    USING 
    (
        SELECT DISTINCT 
            CODIGO_ORIGEN,
            FIRST_VALUE(CONTRATO) OVER (PARTITION BY CODIGO_ORIGEN ORDER BY FECHA DESC) MAX_CONTRATO
        FROM TABLE CONTRATOS
    ) C2 ON (C.CONTRATO = C2.MAX_CONTRATO)
    WHEN MATCHED THEN UPDATE SET ESTADO = 'E';

但是在看到它太慢(两种方法)之后,我尝试使用可以插入有效行的临时表来执行此操作:TEMP_CONTRATOS

    Note: This point goes pretty fast (15 min)

    INSERT INTO TEMP_CONTRATOS (MAX_CONTRATO, CODIGO_ORIGEN)
    SELECT DISTINCT 
            CODIGO_ORIGEN,
            FIRST_VALUE(CONTRATO) OVER (PARTITION BY CODIGO_ORIGEN ORDER BY FECHA DESC) MAX_CONTRATO
    FROM TABLE CONTRATOS;

    Note: Here is the problem again

    UPDATE CONTRATOS C
    SET ESTADO = 'E'
    WHERE NOT EXISTS
    (
        SELECT 1
        FROM TEMP_CONTRATOS TMP
        WHERE C.CONTRATO = TMP.MAX_CONTRATO
    );

我在两个表的列(CONTRATO/MAX_CONTRATO 和 CODIGO_ORIGEN)上都有一个索引,所以我可以提高性能(对吗?)但我的结果仍然太慢...

执行计划为 2700 万条记录提供了超过 700.000 的成本……考虑到记录的数量,是太多还是可以?它正在对两个表进行 TABLE FULL SCAN。

使用提示是个好主意吗?浏览索引是个好主意吗?...最好的选择是什么?使用合并或存在/不存在/不存在??

另外,我不确定是否要为 ESTADO 列创建索引,因为我正在更新该字段并且我读到在执行更新的列中创建索引不是一个好主意......但我有做很多查询加入它。

我正在使用 V$SESSION_LONGOPS 视图和 sysdba 用户的会话视图来监控进程...还有更好的选择吗?

我不知道我是否忘记了一些重要的事情,我想我会的。

请给我一些光。任何建议表示赞赏。

提前谢谢你

问候。

【问题讨论】:

  • 您希望(大致)如何更新行?
  • 我预计会更新 2000 万条记录:总共有 2700 万条记录,其中 650 万条是唯一的......所以.. 应该会影响 2000 万条记录。
  • 能否添加上一次尝试的执行计划?临时表中有 6M 行,最好不要有索引访问计划(即 NL 连接)。
  • 您可能正在将合并重写为 CTAS(创建表...作为选择);转换与修改。它会更快,您可以从压缩中受益;如果你有能力,你可以使用直接路径并使用并行。
  • 我最近尝试的是更新与时态表匹配的那些,而不是更新那些不匹配的。这样我们正在更新 700 万条记录而不是 2000 万……然后将其余的更新为未找到……我不知道我是否解释得很好……是吗?谢谢!

标签: database oracle performance indexing plsql


【解决方案1】:

只是为了跟进转换与修改的好处。我创建了一个包含 2900 万行的“bigemp”表。然后我执行了 un update,这将修改其中大约 2000 万行:

update bigemp
set sal=sal*1.1
where mod( empno, 10 ) > 2

这需要 1053 秒

使用转换技术:

create table bigempNew
as
select empno, ename, job, mgr, hiredate
    case when( mod( empno,10 ) > 2 ) then sal * 1.1
         else sal
    end as sal
    , comm, deptno
from bigemp

这花了 31 秒

这一切都是串行完成的(不使用并行执行)。这就是我提倡这种技术的原因。

【讨论】:

  • 嗨,BobC,是的,我决定使用这种技术,并且我的流程获得了更好的性能。事实上,我需要熟悉它,因为我知道更新和大表不是好朋友。感谢您的宝贵时间。
【解决方案2】:

请看看这是否有帮助..

    merge into contratos c
            using 
            (   select /*+ parallel */ rowid rw,
                    codigo_origen, 
                    first_value(contrato) over (partition by codigo_origen order by fecha desc) max_contrato
                from contratos
            ) c2 on (c.contrato <> c2.max_contrato -- "NOT FOUND" records
                    and c.rowid = c2.rw
            ) when matched then update set estado = 'E'
            ;

此外,您可能希望收集统计数据以让优化器选择最佳计划

【讨论】:

  • 感谢您的贡献!我会看看你的解决方案
【解决方案3】:

让我尝试使用标准 EMP 表解释我的想法。

原理是,与其从 27M 中更新 20M 行,不如用正确的数据重新创建 27M 行的表会更有效率。这种“修改为转换”的一个非常简单的例子是:

update emp
set sal=sal*1.1
where deptno=20

变成

create table empnew as
select empno, ename, job, mgr, hiredate, comm, deptno
  , case when deptno=20 then sal*1.1
         else sal
    end as sal
from emp

然后你重命名表。

在您的示例中,您正在寻找“缺失”的行。一种技术是使用外连接并在键中查找 NULL。

因此,如果我有一个名为 tempempemp 表的副本,那么我可以编写如下查询:

select nvl( t.deptno, -1 ) status, t.*, e.*
from emp e
left join 
    ( select distinct deptno
    , first_value( hiredate ) over (partition by deptno order by sal ) fv
    from tempemp ) t
on t.fv = e.hiredate

在这里,我正在检测“缺失”值并将状态列设置为 -1。

在您的情况下,我认为您可以编写类似的查询

select nvl( c1.contracto, 'E' ) estado, c2.*, c1.*
from CONTRATOS c1
left join
    ( SELECT DISTINCT 
                CODIGO_ORIGEN,
                FIRST_VALUE(CONTRATO) 
                OVER (PARTITION BY CODIGO_ORIGEN 
                      ORDER BY FECHA DESC) MAX_CONTRATO
            FROM  CONTRATOS
     ) c2
on C.CONTRATO = C2.MAX_CONTRATO

如果这是正确的,那么您可以将其转换为 CREATE TABLE AS (CTAS) 语句。此时,您可以利用并行执行、直接路径加载和无日志记录。

希望对你有帮助

【讨论】:

  • BobC,首先,感谢您花时间提供帮助 :) 我理解您的意思,但是:CONTRATOS 表是我需要维护的批处理过程的一部分。我可以创建新表,但最正确的做法是将逻辑保留在 CONTRATOS 表中,正如我所说的……该表还有其他索引,这只是对许多其他索引的验证。 CONTRATOS 表是一个包含客户所有帐户的表,该表有 2700 万条记录,但只有 700 万条是相关的。其他 2000 万需要通过在 ESTADO 列上设置值“E”来标记。
  • 如果它是批处理的一部分,那么这就是使用转换技术的更多理由。我建议你至少做个原型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多