Una discussione di lunga data nei forum e nei newsgroup Oracle è stata l'efficienza dell'utilizzo di count(*) per restituire un conteggio di righe da una determinata tabella. Una nuova ruga in quella discussione ora introduce count(rowid) come un'alternativa più efficiente; l'argomento afferma che count(*) espande l'intero elenco di colonne, proprio come "select * ..." e, come tale, potrebbe essere un sink di risorse quando le colonne CLOB sono presenti nella tabella desiderata. Diamo un'occhiata a quell'argomento e vediamo se regge. Iniziamo creando e popolando una tabella contenente una colonna CLOB:
SQL> SQL> create table count_test( 2 id number, 3 val varchar2(40), 4 clb clob); Table created. SQL> SQL> begin 2 for z in 1..1000000 loop 3 insert into count_test 4 values(z, 'Record '||z, 'Clob value '||z); 5 end loop; 6 7 commit; 8 end; 9 / PL/SQL procedure successfully completed. SQL>
Quindi impostiamo l'evento 10053 per scaricare la traccia dell'ottimizzatore in modo da poter vedere come Oracle prevede di eseguire le query count():
SQL> alter session set events = '10053 trace name context forever, level 2'; Session altered.
La fase è pronta, eseguiamo alcune varianti di count() per vedere come si comporta Oracle. Innanzitutto, eseguiremo un conteggio diretto(*) e visualizzeremo il piano:
SQL> select count(*) from count_test; COUNT(*) ---------- 1000000 SQL> alter session set events = '10053 trace name context off'; Session altered. SQL> explain plan for select count(*) from count_test; Explained. SQL> select * from table(dbms_xplan.display(null,null,'projection')); PLAN_TABLE_OUTPUT ---------------------------------------------------------------------------------------------------------- Plan hash value: 371675025 ----------------------------------------+-----------------------------------+ | Id | Operation | Name | Rows | Bytes | Cost | Time | ----------------------------------------+-----------------------------------+ | 0 | SELECT STATEMENT | | | | 3582 | | | 1 | SORT AGGREGATE | | 1 | | | | | 2 | TABLE ACCESS FULL | COUNT_TEST| 848K | | 3582 | 00:00:43 | ----------------------------------------+-----------------------------------+ Column Projection Information (identified by operation id): ----------------------------------------------------------- 1 - (#keys=0) COUNT(*)[22] 2 - (rowset=1019) Note ----- - dynamic statistics used: dynamic sampling (level=2) 19 rows selected. SQL>
Osservando il file di traccia generato, Oracle utilizza semplicemente count(*) così com'è per restituire i risultati:
Final query after transformations:******* UNPARSED QUERY IS ******* SELECT COUNT(*) "COUNT(*)" FROM "BING"."COUNT_TEST" "COUNT_TEST" ... ----- Explain Plan Dump ----- ----- Plan Table ----- ============ Plan Table ============ ----------------------------------------+-----------------------------------+ | Id | Operation | Name | Rows | Bytes | Cost | Time | ----------------------------------------+-----------------------------------+ | 0 | SELECT STATEMENT | | | | 3582 | | | 1 | SORT AGGREGATE | | 1 | | | | | 2 | TABLE ACCESS FULL | COUNT_TEST| 848K | | 3582 | 00:00:43 | ----------------------------------------+-----------------------------------+ Query Block Name / Object Alias (identified by operation id): ------------------------------------------------------------ 1 - SEL$1 2 - SEL$1 / "COUNT_TEST"@"SEL$1" ------------------------------------------------------------ Predicate Information: ------------------------ SQL>
Nessuna sorpresa lì; si noti che Oracle non espande il "*" a tutte le colonne della tabella:il "*" in questo caso indica che tutte le righe devono essere contate. Se fosse stato fornito un nome di colonna effettivo, Oracle avrebbe contato i valori nella colonna specificata. Diamo ora un'occhiata a cosa fa Oracle con una query count(rowid):
SQL> alter session set events = '10053 trace name context forever, level 2'; Session altered. SQL> select count(rowid) from count_test; COUNT(ROWID) ------------ 1000000 SQL> alter session set events = '10053 trace name context off'; Session altered. SQL> explain plan for select count(rowid) from count_test; Explained. SQL> select * from table(dbms_xplan.display(null,null,'projection')); PLAN_TABLE_OUTPUT --------------------------------------------------------------------------------------------------- Plan hash value: 371675025 ----------------------------------------+-----------------------------------+ | Id | Operation | Name | Rows | Bytes | Cost | Time | ----------------------------------------+-----------------------------------+ | 0 | SELECT STATEMENT | | | | 3582 | | | 1 | SORT AGGREGATE | | 1 | 12 | | | | 2 | TABLE ACCESS FULL | COUNT_TEST| 848K | 9941K | 3582 | 00:00:43 | ----------------------------------------+-----------------------------------+ Column Projection Information (identified by operation id): ----------------------------------------------------------- 1 - (#keys=0) COUNT(ROWID)[22] 2 - (rowset=256) ROWID[ROWID,10] Note ----- - dynamic statistics used: dynamic sampling (level=2) 19 rows selected. SQL>
Oracle genera un valore rowid per ogni riga della tabella, un'operazione che consumerà alcune risorse della CPU. Poiché la query è stata restituita all'incirca nello stesso tempo della versione count(*), il "hit" delle prestazioni sembra essere trascurabile. L'aggiunta di una chiave primaria modifica leggermente i piani ma non il testo della query:
SQL> alter table count_test add constraint count_pk primary key(id); Table altered. SQL> SQL> alter session set events = '10053 trace name context forever, level 2'; Session altered. SQL> select count(*) from count_test; COUNT(*) ---------- 1000000 SQL> alter session set events = '10053 trace name context off'; Session altered. SQL> explain plan for select count(*) from count_test; Explained. SQL> select * from table(dbms_xplan.display(null,null,'projection')); PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------------------ Plan hash value: 371675025 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 589 (2)| 00:00:01 | | 1 | SORT AGGREGATE | | 1 | | | | 2 | INDEX FAST FULL SCAN| COUNT_PK | 848K| 589 (2)| 00:00:01 | -------------------------------------------------------------------------- Column Projection Information (identified by operation id): ----------------------------------------------------------- 1 - (#keys=0) COUNT(*)[22] 2 - (rowset=1019) Note ----- - dynamic statistics used: dynamic sampling (level=2) 19 rows selected. SQL> SQL> SQL> alter session set events = '10053 trace name context forever, level 2'; Session altered. SQL> select count(rowid) from count_test; COUNT(ROWID) ------------ 1000000 SQL> alter session set events = '10053 trace name context off'; Session altered. SQL> explain plan for select count(rowid) from count_test; Explained. SQL> select * from table(dbms_xplan.display(null,null,'projection')); PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------ Plan hash value: 371675025 ---------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 12 | 589 (2)| 00:00:01 | | 1 | SORT AGGREGATE | | 1 | 12 | | | | 2 | INDEX FAST FULL SCAN| COUNT_PK | 848K| 9941K| 589 (2)| 00:00:01 | ---------------------------------------------------------------------------------- Column Projection Information (identified by operation id): ----------------------------------------------------------- 1 - (#keys=0) COUNT(ROWID)[22] 2 - (rowset=256) ROWID[ROWID,10] Note ----- - dynamic statistics used: dynamic sampling (level=2) 19 rows selected. SQL> SQL> spool off commit;
I dettagli della traccia 10053 non sono cambiati dopo l'aggiunta della chiave primaria.
Sembrerebbe che due informazioni siano state raccolte da questo esperimento:count(rowid) non è migliore di count(*) quando le tabelle contengono colonne CLOB e quel conteggio(*) non espande l'elenco delle colonne come fa "select *" (e non c'è motivo di credere che dovrebbe).
La prova è nel budino, come recita il vecchio adagio.
# # #
Vedi gli articoli di David Fitzjarrell