Pessoal, para Exemplificar os Conceitos discutidos montei aqui um caso-teste, 
empregando paralelismo de pobre, DELETE com array processing e query com bulk 
collecting, e com isso (num ambiente DE TESTE, uma VM no meu notebook) Deletei 
mais de 7 milhões de linhas (e não linhas quaisquer, mas linhas que não estavam 
na tabela de "cópia" dos dados, o que foi a proposição da thread) em minutos...
 ÓBVIO ULULANTE que num ambiente real com TOTAL certeza a pessoa vai ter outras 
tasks rodando, vai ter alguma concorrência feroz que num ambiente de teste não 
temos, E com certeza devem haver índices a atualizar e constraints a checar 
(DIFICILMENTE se obtém em Prod um Honesto e Suculento table scan que nem o que 
eu obtive, dado que a tabela a deletar no meu exemplo Não Tinha nem índices nem 
constraints nem relacionamentos), então CLARO que não se vai obter uma 
performance do tipo, mas o case demonstra bem a Eficiência das técnicas ...
 E antes de mostrar o Exemplo, FRISO que este Código é APENAS PARA DEMONSTRAÇÃO 
: ele NÂO ESTÁ PRONTO para produção , Não prevê SAVE EXCEPTIONS, não prevê a 
possibilidade de o intervalo das ROWIDs ter mudado por eventuais DMLs 
concorrentes, está limitado na qtdade de recursos de máquina que eu boto no 
panelão, então COM CERTEZA ABSOLUTA não recomendo que ninguém o bote nem perto 
de produção, mas segue, FOR DEMO ONLY :

=> tenho uma tabela menor com as linhas a preservar :

SYSTEM:@orcl:SQL>select count(*) from TAB_PRESERVE;

  COUNT(*)
----------
    150006

=> tenho uma tabela Grande que contém os registros a serem deletados (teste com 
pouco mais de 7 milhões e meio) :

SYSTEM:@orcl:SQL>select count(*) from TAB_TO_DELETE;

  COUNT(*)
----------
   7500200

SYSTEM:@orcl:SQL>

=> como vou fazer Paralelismo de pobre (já que pro caso perguntado a pessoa não 
tem Enterprise Edition, então neca de parallel sql), eu DIVIDO a tabela em 3 
partes, 3 INTERVALOS : 

SYSTEM:SQL>get split.sql
  1  set verify off
  2  define TNAME=&1
  3  define CHUNKS=&2
  4  --
  5  select grp,
  6         dbms_rowid.rowid_create( 1, data_object_id, lo_fno, lo_block, 0 ) 
min_rid,
  7         dbms_rowid.rowid_create( 1, data_object_id, hi_fno, hi_block, 10000 
) max_rid
  8    from (
  9  select distinct grp,
 10         first_value(relative_fno)
 11          over (partition by grp order by relative_fno, block_id
 12                rows between unbounded preceding and unbounded following) 
lo_fno,
 13         first_value(block_id    )
 14         over (partition by grp order by relative_fno, block_id
 15              rows between unbounded preceding and unbounded following) 
lo_block,
 16         last_value(relative_fno)
 17          over (partition by grp order by relative_fno, block_id
 18          rows between unbounded preceding and unbounded following) hi_fno,
 19         last_value(block_id+blocks-1)
 20          over (partition by grp order by relative_fno, block_id
 21           rows between unbounded preceding and unbounded following) 
hi_block,
 22         sum(blocks) over (partition by grp) sum_blocks
 23    from (
 24  select relative_fno,
 25         block_id,
 26         blocks,
 27         trunc( (sum(blocks) over (order by relative_fno, block_id)-0.01) /
 28                (sum(blocks) over ()/&CHUNKS) ) grp
 29    from dba_extents
 30   where segment_name = upper('&TNAME')
 31     and owner = user order by block_id
 32         )
 33         ),
 34*        (select data_object_id from user_objects where object_name = 
upper('&TNAME') )
SYSTEM:SQL>

SYSTEM:SQL>@split
Informe o valor para 1: TAB_TO_DELETE
Informe o valor para 2: 3

       GRP MIN_RID            MAX_RID
---------- ------------------ ------------------
         0 AAAa1QAAIAAAACAAAA AAAa1QAAIAAAIx/CcQ
         1 AAAa1QAAIAAAIyAAAA AAAa1QAAIAAARh/CcQ
         2 AAAa1QAAIAAARiAAAA AAAa1QAAIAAAah/CcQ

=> ok... Antes de executar, primeiro vou desabilitar Paralelismo, já que estou 
usando um Enterprise Edition onde o recurso existe, de modo a Simular a 
situação lá do colega que usa Standard Edition :

-> sess#1 :

SYSTEM#1:SQL>ALTER session disable parallel query;

SessÒo alterada.

SYSTEM#1:SQL>ALTER session disable parallel dml;

SessÒo alterada.

SYSTEM#1:SQL>

-> sess#2 :

SYSTEM#2:SQL>ALTER session disable parallel query;

SessÒo alterada.

SYSTEM#2:SQL>ALTER session disable parallel dml;

SessÒo alterada.

-> sess#3 :

SYSTEM#3:SQL>ALTER session disable parallel query;

SessÒo alterada.

SYSTEM#3:SQL>ALTER session disable parallel dml;

SessÒo alterada.

==> PREPARO cada sessão, alocando o máximo de recursos que posso (mostrarei 
aqui da sessão#1, mas fiz para TODAs elas):

SYSTEM#1:SQL>alter session set db_file_multiblock_read_count=128;

SessÒo alterada.

SYSTEM#1:SQL>alter session set workarea_size_policy=MANUAL;

SessÒo alterada.

SYSTEM#1:SQL>alter session set SORT_AREA_SIZE=104857600;

SessÒo alterada.

SYSTEM#1:SQL>alter session set HASH_AREA_SIZE=209715200;

SessÒo alterada.

SYSTEM#1:SQL>set timing on

==> ok , agora é executar a rotina simultaneamente nas 3 sessões, cada uma 
lendo partes diferentes da tabela grande e deletando linhas diferentes :

-> exec. na sess#1

SYSTEM#1:SQL>DECLARE
   cursor v_cursor is
   SELECT -- Cursor#1
     a.rowid ROWID_A_DELETAR
     FROM tab_to_delete a
    WHERE a.rowid between 'AAAa1QAAIAAAACAAAA' and 'AAAa1QAAIAAAIx/CcQ'
      AND NOT EXISTS
       (select 1 from tab_preserve b where b.object_id = a.object_id);
    TYPE t_rowid IS TABLE OF rowid INDEX BY BINARY_INTEGER;
    v_cursor_array t_rowid;
    v_pass   NUMBER := 0;
    v_commit NUMBER := 0;
BEGIN
   OPEN v_cursor;
   LOOP
      FETCH v_cursor BULK COLLECT INTO v_cursor_array LIMIT 2000;
      EXIT WHEN v_cursor%NOTFOUND;
      v_pass := v_pass + 1;
      dbms_application_info.set_client_info('Sess#1,Pass#' || v_pass || ' em ' 
|| to_char(sysdate, 'HH24:MI:SS'));
      forall i in v_cursor_array.FIRST..v_cursor_array.LAST DELETE FROM 
tab_to_delete WHERE rowid=v_cursor_array(i);
      v_pass := v_pass + 1;
      v_commit := v_commit + 1;
      if v_commit = 100 then
         commit;
         v_commit := 0;
      end if;
   END LOOP;
   CLOSE v_cursor;
   COMMIT;
 END;
/
..... rola em background ...

-> exec. na sess#2

SYSTEM#2:SQL>DECLARE
   cursor v_cursor is
   SELECT -- Cursor#2
     a.rowid ROWID_A_DELETAR
     FROM tab_to_delete a
    WHERE a.rowid between 'AAAa1QAAIAAAIyAAAA' and 'AAAa1QAAIAAARh/CcQ'
      AND NOT EXISTS
       (select 1 from tab_preserve b where b.object_id = a.object_id);
    TYPE t_rowid IS TABLE OF rowid INDEX BY BINARY_INTEGER;
    v_cursor_array t_rowid;
    v_pass   NUMBER := 0;
    v_commit NUMBER := 0;
BEGIN
   OPEN v_cursor;
   LOOP
      FETCH v_cursor BULK COLLECT INTO v_cursor_array LIMIT 2000;
      EXIT WHEN v_cursor%NOTFOUND;
      v_pass := v_pass + 1;
      dbms_application_info.set_client_info('Sess#2,Pass#' || v_pass || ' em ' 
|| to_char(sysdate, 'HH24:MI:SS'));
      forall i in v_cursor_array.FIRST..v_cursor_array.LAST DELETE FROM 
tab_to_delete WHERE rowid=v_cursor_array(i);
      v_pass := v_pass + 1;
      v_commit := v_commit + 1;
      if v_commit = 100 then
         commit;
         v_commit := 0;
      end if;
   END LOOP;
   CLOSE v_cursor;
   COMMIT;
 END;
/
... continua em background ...

-> exec. na sess#3

SYSTEM#3:SQL>DECLARE
   cursor v_cursor is
   SELECT -- Cursor#3
     a.rowid ROWID_A_DELETAR
     FROM tab_to_delete a
    WHERE a.rowid between 'AAAa1QAAIAAARiAAAA' and 'AAAa1QAAIAAAah/CcQ'
      AND NOT EXISTS
       (select 1 from tab_preserve b where b.object_id = a.object_id);
    TYPE t_rowid IS TABLE OF rowid INDEX BY BINARY_INTEGER;
    v_cursor_array t_rowid;
    v_pass   NUMBER := 0;
    v_commit NUMBER := 0;
BEGIN
   OPEN v_cursor;
   LOOP
      FETCH v_cursor BULK COLLECT INTO v_cursor_array LIMIT 2000;
      EXIT WHEN v_cursor%NOTFOUND;
      v_pass := v_pass + 1;
      dbms_application_info.set_client_info('Sess#3,Pass#' || v_pass || ' em ' 
|| to_char(sysdate, 'HH24:MI:SS'));
      forall i in v_cursor_array.FIRST..v_cursor_array.LAST DELETE FROM 
tab_to_delete WHERE rowid=v_cursor_array(i);
      v_pass := v_pass + 1;
      v_commit := v_commit + 1;
      if v_commit = 100 then
         commit;
         v_commit := 0;
      end if;
   END LOOP;
   CLOSE v_cursor;
   COMMIT;
 END;
/
.... continua em background ...

==> durante a execução, Notar que o progresso está rápido, muitas centenas de 
passadas/envios de arrays por minuto :

SYS:AS SYSDBA@orcl:SQL>select client_info from v$session where client_info is 
not null;

CLIENT_INFO
----------------------------------------------------------------
Sess#1,Pass#903 em 08:45:02
Sess#2,Pass#111 em 08:46:30
Sess#3,Pass#305 em 08:45:31

... espero um pouquinho e consulto de novo ...

SYS:AS SYSDBA@orcl:SQL>/

CLIENT_INFO
----------------------------------------------------------------
Sess#1,Pass#1233 em 08:48:55
Sess#2,Pass#579 em 08:48:03
Sess#3,Pass#787 em 08:48:24

=> resultado final :

-> sess#1 :

PL/SQL procedure successfully completed.

Elapsed: 00:06:27.65
SYSTEM#1:SQL>

-> sess#2 :

PL/SQL procedure successfully completed.

Elapsed: 00:06:39.93
SYSTEM#2:SQL>

-> sess #3 :

PL/SQL procedure successfully completed.

Elapsed: 00:06:40.34

=====>> EVIDÊNCIA de DELETEs feitos :

SYS:AS SYSDBA@orcl:SQL>select count(*) from system.tab_preserve;

  COUNT(*)
----------
    150006

SYS:AS SYSDBA@orcl:SQL>select count(*) from system.tab_to_delete a
  2* where exists (select 1 from system.tab_preserve b where b.object_id = 
a.object_id);

  COUNT(*)
----------
    150006

SYS:AS SYSDBA@orcl:SQL>

-> veja que havia alguns OBJECT_IDs (a "chave") órfãos  : 

SYS:AS SYSDBA@orcl:SQL>select count(*) from system.tab_to_delete;

  COUNT(*)
----------
    152200
    
SYS:AS SYSDBA@orcl:SQL>select count(*) from system.tab_to_delete a
  2  where not exists (select 1 from system.tab_preserve b where b.object_id = 
a.object_id);

  COUNT(*)
----------
      2194

[]s

  Chiappa

 

---Em oracle_br@yahoogrupos.com.br, <jlchia...@yahoo.com.br> escreveu:

 Alessandro, Sim, absolutamente não tem o que discutir que cada COMMIT te força 
a ESPERAR que os logs sejam salvos em disco, a esperar que as alterações que o 
RDBMS faz nas suas tabelas internas se completem e sejam salvas, esperar e 
esperar, e assim se deveria comittar o menos possível : 
https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4951966319022
 , 
https://asktom.oracle.com/pls/asktom/f?p=100:11:0::NO::P11_QUESTION_ID:6407993912330
 e 
https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::p11_question_id:5918938803188
 tem testes e demonstrações que exemplificam Claramente...
 Note porém que no caso do código do lmarinho, o COMMIT tá depois do DELETE em 
array/bulk (DELETE que está logo depois do FORALL), ele Não É linha-a-linha, 
não é cada linha que está sendo COMMITada, é o array.... 
 Logicamente, o código do lmarinho vale de exemplo para outros cenários mas 
penso ser inadequado pro caso do colega que estava perguntando porque :
 
 a. as linhas a serem deletadas devem ser encontradas via JOIN, portanto seria 
necessário um carregamento com BULK COLLECT pro array, para que depois o DELETE 
possa as encontrar, provavelmente via rowid
 
 b. os volumes envolvidos inviabilizam se ter um Array de 
não-sei-quantos-milhões, ou mesmo centenas e centenas de milhares de linhas, 
entãao não daria pra fazer um FORALL DELETE único, julgo que teria que ter um 
outro LOOP e commit de tanto em tanto tempo ou de tantas em tantas linhas, para 
não esgotar a área de rollback/undo - normalmente se tem uma área de 
rollback/undo adequada ao dia-a-dia, e o tal DELETE de várias dezenas de 
milhões que o colega queria não é algo rotineiro, provavelmente o que ele tem 
hoje não Suporta, por isso que foi recomendado assumir os WAITs e a demora  e 
comitar múltiplas vezes
 
 []s
 
   Chiappa
  

Responder a