Picture of Roberto Sobrinho
Roberto Sobrinho

28/06/2024

DELETE em Lote no Oracle: Comparando BULK COLLECT e FORALL com Métodos Tradicionais

Os comandos BULK COLLECT e FORALL são essenciais para o processamento em lote no Oracle, permitindo a manipulação eficiente de grandes conjuntos de dados. Eles minimizam a troca de contexto entre o SQL e o PL/SQL, resultando em melhorias significativas de desempenho.

  • BULK COLLECT: Permite carregar múltiplas linhas em coleções PL/SQL em uma única operação.
  • FORALL: Permite realizar operações DML em lote utilizando coleções PL/SQL.

Utilização do LIMIT com BULK COLLECT e FORALL

Ao utilizar BULK COLLECT, é essencial considerar o uso do parâmetro LIMIT para controlar o número de registros processados em cada operação. Isso ajuda a evitar o consumo excessivo de memória e melhora a eficiência do processamento em lote.

    Exemplo Simples:

    SET SERVEROUTPUT ON;
    DECLARE
      CURSOR c1 IS
        SELECT rowid
        FROM exemplo_tabela
        WHERE id <= 10000;
    
      TYPE t_rowid_tab IS TABLE OF ROWID INDEX BY PLS_INTEGER;
      v_rowid_tab t_rowid_tab;
    
      v_inicio NUMBER;
      v_total_deletados INTEGER := 0;
    BEGIN
      v_inicio := dbms_utility.get_time;
    
      OPEN c1;
      LOOP
        FETCH c1 BULK COLLECT INTO v_rowid_tab LIMIT 1000;
        EXIT WHEN v_rowid_tab.COUNT = 0;
    
        FORALL i IN v_rowid_tab.FIRST .. v_rowid_tab.LAST
          DELETE FROM exemplo_tabela WHERE rowid = v_rowid_tab(i);
    
        v_total_deletados := v_total_deletados + v_rowid_tab.COUNT;
      END LOOP;
      CLOSE c1;
    
      DBMS_OUTPUT.PUT_LINE('Total de Linhas Deletadas: ' || v_total_deletados);
      DBMS_OUTPUT.PUT_LINE('Tempo de Execução: ' || ROUND((dbms_utility.get_time - v_inicio) / 100, 2) || ' segundos');
    EXCEPTION
      WHEN OTHERS THEN
        ROLLBACK;
        RAISE;
    END;
    /
    
    

    Explicação:

    • Cursor c1: Define um cursor que seleciona os rowid das linhas que queremos deletar.
    • t_rowid_tab: Declara um tipo de tabela PL/SQL para armazenar os ROWIDs.
    • v_rowid_tab: Declara uma variável do tipo t_rowid_tab.
    • v_inicio: Usado para armazenar o tempo de início da operação.
    • v_total_deletados: Contador para o total de linhas deletadas.
    • LIMIT 1000: Carrega até 1000 linhas por vez em cada operação de fetch para evitar sobrecarga de memória.
    • FORALL: Utiliza a coleção de ROWIDs para deletar as linhas em lote de forma eficiente.

    Este bloco de código ilustra como usar BULK COLLECT e FORALL com o parâmetro LIMIT para deletar dados de uma tabela de maneira eficiente, controlando a quantidade de dados processados em cada operação.

    Gerando Dados para Realizar Teste de Desepenho

    Criaremos a tabela CASE_0003_R_SCR com base na dba_objects.

    CREATE TABLE CASE_0003_R_SCR AS
    SELECT
      rownum AS ID_X,
      d.object_id,
      d.object_name,
      d.owner,
      d.object_type,
      d.status,
      d.created
    FROM dba_objects d
    CROSS JOIN (SELECT LEVEL FROM dual CONNECT BY LEVEL <= 10)
    WHERE rownum <= 1500000;
    
    ALTER TABLE CASE_0003_R_SCR ADD CONSTRAINT pk_CASE_0003_R_SCR PRIMARY KEY (ID_X);
    
    EXEC DBMS_STATS.GATHER_TABLE_STATS(ownname => USER, tabname => 'CASE_0003_R_SCR');
    

    Executar Bloco PL/SQL Usando BULK COLLECT e FORALL para DELETE

    ALTER TABLE CASE_0003_R_SCR MOVE;
    ALTER INDEX pk_CASE_0003_R_SCR REBUILD;
    
    SET SERVEROUTPUT ON;
    DECLARE
      cursor c1 is
        select x.rowid as rw
          from CASE_0003_R_SCR x
         where OBJECT_TYPE in ('JAVA CLASS', 'SYNONYM', 'VIEW', 'JAVA RESOURCE', 'JAVA DATA', 'TABLE', 'INDEX', 'PACKAGE', 'PACKAGE BODY');
      type t_rowid is table of rowid index by pls_integer;
      row_t_rowid t_rowid;
      v_inicio    NUMBER;
      v_conta     INTEGER := 0;
    BEGIN
      v_inicio := dbms_utility.get_time;
      open c1;
      loop
        fetch c1 bulk collect
          into row_t_rowid limit 1000;
        exit when row_t_rowid.count = 0;
    	v_conta := v_conta + row_t_rowid.count;
      
        forall i in row_t_rowid.first .. row_t_rowid.last
          delete CASE_0003_R_SCR where rowid = row_t_rowid(i);
      --
      ROLLBACK;
      end loop;
      close c1;
      --/
      ROLLBACK;
      --/
      dbms_output.put_line('=======================================================================');
      dbms_output.put_line('TOTAL DE LINHAS DE DELETE    :    ' || v_conta);
      dbms_output.put_line('TEMPO COM BULK COLLECT/FORALL:    ' || trim(TO_CHAR(ROUND((dbms_utility.get_time - v_inicio) / 100, 2), '999900.99') || ' Segundos'));
      dbms_output.put_line('=======================================================================');
      --/
    exception
      when others then
          raise_application_error(-20010,'Backtrace: ' ||dbms_utility.format_error_backtrace ||' Sqlerrm: ' || substr(sqlerrm, 1, 300));
    end;
    /
    

      Executar Bloco PL/SQL sem Usar BULK COLLECT e FORALL

      ALTER TABLE CASE_0003_R_SCR MOVE;
      ALTER INDEX pk_CASE_0003_R_SCR REBUILD;
      
      SET SERVEROUTPUT ON;
      DECLARE
        cursor c1 is
          select x.rowid as rw
            from CASE_0003_R_SCR x
          where OBJECT_TYPE in ('JAVA CLASS', 'SYNONYM', 'VIEW', 'JAVA RESOURCE', 'JAVA DATA', 'TABLE', 'INDEX', 'PACKAGE', 'PACKAGE BODY');
        v_del    c1%ROWTYPE;
        v_inicio    NUMBER;
        v_conta     INTEGER := 0; 
        v_save      INTEGER := 0;
      BEGIN
        v_inicio := dbms_utility.get_time;
        OPEN c1;
        LOOP
          FETCH c1
            INTO v_del;
          EXIT WHEN c1%NOTFOUND;
          delete CASE_0003_R_SCR where rowid = v_del.rw;
      	v_conta := v_conta +1;
      	v_save  := v_save  +1;
      	if v_save >= 1000 
      	then
      	    ROLLBACK;
      		v_save := 0;
          end if;
        end loop;
        close c1;
        ROLLBACK;
        --/
        dbms_output.put_line('=============================================================================================');
        dbms_output.put_line('TOTAL DE LINHAS DE DELETE    :    ' || v_conta);
        dbms_output.put_line('TEMPO SEM BULK COLLECT/FORALL:    ' || trim(TO_CHAR(ROUND((dbms_utility.get_time - v_inicio) / 100, 2), '999900.99') || ' Segundos'));
        dbms_output.put_line('=============================================================================================');
        --/
      exception
        when others then
            raise_application_error(-20010,'Backtrace: ' ||dbms_utility.format_error_backtrace ||' Sqlerrm: ' || substr(sqlerrm, 1, 300));
      end;
      /
      

      Comparação de Desempenho

      Podemos ver a diferença significativa no tempo de execução entre os dois métodos. Usando BULK COLLECT e FORALL, conseguimos deletar 685920 linhas em 72,46 segundos. No método tradicional, o mesmo número de linhas levou 527,98 segundos para ser deletado. Isso demonstra a eficácia do processamento em lote para operações de grande volume.

      Melhora em Percentual:

      Melhora de Desempenho: ((527.98 - 72.46) / 527.98) * 100 = 86.28%

      A utilização de BULK COLLECT e FORALL proporcionou uma melhora de desempenho de aproximadamente 86,28%.

        Vantagens do Processamento em Lote com BULK COLLECT e FORALL

        • Redução de Trocas de Contexto Minimizando a troca de contexto entre SQL e PL/SQL, o desempenho é significativamente melhorado. Cada troca de contexto entre o PL/SQL e o SQL pode adicionar uma sobrecarga de tempo. Com BULK COLLECT e FORALL, o número dessas trocas é reduzido, tornando a operação mais eficiente.
        • Manipulação de Grandes Conjuntos de Dados BULK COLLECT e FORALL são ideais para operações que envolvem grandes volumes de dados. Quando precisamos realizar operações DML em milhares ou milhões de linhas, essas técnicas permitem que a operação seja feita de maneira muito mais rápida do que se fosse feita linha por linha.
        • Código Mais Limpo e Manutenível O uso de coleções e operações em lote pode resultar em um código mais organizado e mais fácil de manter. Em vez de ter loops complexos e numerosas operações DML individuais, podemos simplificar a lógica com BULK COLLECT e FORALL.
        • Melhoria na Performance Geral Como vimos nos resultados de desempenho, a utilização dessas técnicas pode resultar em melhorias significativas de tempo. Essa melhoria de performance pode liberar recursos do sistema, permitindo que outras operações sejam executadas de maneira mais eficiente.

        Script de Teste Disponível no GitHub

        Para que você possa testar essa solução em seu próprio ambiente, disponibilizei um script no GitHub que gera uma tabela de teste chamada CASE_0003_R_SCR e executa o passo a passo da solução. Após a execução do teste, a tabela será automaticamente excluída. Você pode acessar o script aqui.


        O uso de BULK COLLECT e FORALL no Oracle é uma prática essencial para qualquer DBA ou desenvolvedor que deseja otimizar a performance de operações de banco de dados que envolvem grandes volumes de dados. Essas técnicas reduzem significativamente o tempo de execução ao minimizar as trocas de contexto entre SQL e PL/SQL, permitindo um processamento em lote eficiente.

        🚬🍺 #GuinaNãoTinhaDó #SQLTuning #DBAProblematico #CaceteDeAgulha #EngenheiroDeObraPronta #AcacioÉMeuAmigo #AvidaÉLoka #OraclePerformance #OracleDB #DBATips #Mais1PraHistória #VidaLoka 🍺🚬

        Compartilhe

        Facebook
        Twitter
        LinkedIn
        WhatsApp
        Email
        Print

        Pesquisar

        Roberto Sobrinho

        Sou Roberto Fernandes Sobrinho, também conhecido como Sobrinho DBA , pós graduado em “Architecture and Database Administration”, entusiasta, dedicado e com 20 anos de experiência com Oracle Database e suas diversas distribuições e variações.

        Oracle ACE Associate

        2025

        Specialist

        Exadata Database Machine X9M

        Professional

        Oracle Database Administration

        Professional

        Oracle Database 19c: RAC, ASM, & Grid Infra Administrator

        Professional

        Oracle Autonomous Database Cloud

        Professional

        Oracle Cloud Database Migration and Integration

        Professional

        Oracle Database PL/SQL Developer

        Associate

        Oracle Cloud Infrastructure Architect

        Associate

        Oracle Cloud Infrastructure Foundations

        Categorias

        Categorias

        Tags

        DELETE em Lote no Oracle: Comparando BULK COLLECT e FORALL com Métodos Tradicionais