Обновить / СУЩЕСТВУЕТ или MERGE для лучшей производительности? Медленное обновление на большой таблице (миллионы записей) - PullRequest
1 голос
/ 14 февраля 2020

Мне нужна помощь с некоторыми проблемами производительности:

У меня есть таблица CONTRATOS с 27 миллионами записей. Эта таблица имеет несколько столбцов, но важные из них: CONTRATO, CODIGO_ORIGEN.

Может существовать более одного CONTRATO для каждого CODIGO_ORIGEN, поэтому мне нужно сделать это:

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

После этого мы у меня 6 миллионов записей, хорошие ...

Мне нужно обновить записи NOT FOUND (CONTRATO) в таблице CONTRATOS, указав в столбце ESTADO значение 'E'.

Сначала я попытался сделать все за один шаг (запрос):

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), так что я могу улучшить производительность (верно?), но я все еще получаю слишком медленные результаты ...

План выполнения дает мне более 700 000 затрат на 27 миллионов записей ... Это слишком много или это нормально, учитывая количество записей? Он выполняет ТАБЛИЦУ ПОЛНОГО ПРОВЕРКИ обеих таблиц.

Это хорошая идея - использовать подсказки? Это хорошая идея, проходя через индексы? ... Каков наилучший вариант? используя Merge или Exists / Not существует / Not in ??

Кроме того, я не уверен в создании индекса для столбца ESTADO, потому что я обновляю это поле, и я прочитал, что создавать его не очень хорошая идея индекс в столбце, где вы выполняете обновления ... но мне нужно выполнить много запросов, связанных с ним.

Я использую представление V $ SESSION_LONGOPS и представление сеанса пользователя sysdba для мониторинга процесса ... Есть ли лучшая альтернатива?

Я не знаю, если я забуду что-то важное, я думаю, что я делаю.

Принесите мне немного света, пожалуйста. Любой совет приветствуется.

Заранее спасибо

С уважением.

Ответы [ 3 ]

0 голосов
/ 18 февраля 2020

Пожалуйста, посмотрите, поможет ли это ..

    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'
            ;

Также вы можете собирать статистику, чтобы оптимизатор мог выбрать лучший план

0 голосов
/ 21 февраля 2020

Просто чтобы узнать о преимуществах трансформации и модификации. Я создал таблицу "bigemp" с 29 миллионами строк. Затем я выполнил un update, который изменит около 20 миллионов строк:

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 с

Все это было сделано последовательно (без параллельного выполнения). Вот почему я защищаю эту технику.

0 голосов
/ 15 февраля 2020

Позвольте мне попытаться объяснить мое мышление, используя стандартную таблицу EMP.

Принцип заключается в том, что вместо обновления 20M строк из 27M будет более эффективно воссоздать таблицу 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 в ключе.

Так что, если у меня есть копия таблицы emp с именем tempemp, то я мог бы написать запрос примерно так: :

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). На этом этапе вы можете воспользоваться преимуществами параллельного выполнения, прямой загрузки пути и nologging.

Надеюсь, это полезно

...