Una discusión de larga data en los foros y grupos de noticias de Oracle ha sido la eficiencia de usar count (*) para devolver un recuento de filas de una tabla determinada. Una nueva arruga en esa discusión ahora presenta count(rowid) como una alternativa más eficiente; el argumento establece que count(*) expande toda la lista de columnas, como "seleccionar *..." y, como tal, podría ser un sumidero de recursos cuando las columnas CLOB están presentes en la tabla deseada. Echemos un vistazo a ese argumento y veamos si se sostiene. Comencemos por crear y completar una tabla que contenga una columna 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>
A continuación, establezcamos el evento 10053 para volcar el seguimiento del optimizador para que podamos ver cómo planea Oracle ejecutar las consultas count():
SQL> alter session set events = '10053 trace name context forever, level 2'; Session altered.
El escenario está listo, ejecutemos algunas variantes de count() para ver cómo se comporta Oracle. Primero, ejecutaremos un conteo directo (*) y mostraremos el plan:
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>
Mirando el archivo de rastreo generado, Oracle simplemente usa count (*) tal cual para devolver los resultados:
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>
No hay sorpresas allí; observe que Oracle no expande el "*" a todas las columnas de la tabla; el "*" en este caso indica que se deben contar todas las filas. Si se hubiera proporcionado un nombre de columna real, Oracle habría contado los valores en la columna especificada. Veamos ahora lo que hace Oracle con una consulta 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 valor de ID de fila para cada fila de la tabla, una operación que consumirá algunos recursos de la CPU. Dado que la consulta se devolvió aproximadamente al mismo tiempo que la versión de conteo (*), el "golpe" de rendimiento parece ser insignificante. Agregar una clave principal cambia ligeramente los planes, pero no el texto de la consulta:
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;
Los detalles del seguimiento 10053 no cambiaron después de agregar la clave principal.
Parecería que se han extraído dos piezas de información de este experimento:count(rowid) no es mejor que count(*) cuando las tablas contienen columnas CLOB y ese count(*) no expande la lista de columnas como lo hace "select *". (y no hay razón para creer que debería).
La prueba está en el pudín, como dice el viejo adagio.
# # #
Ver artículos de David Fitzjarrell