Een langdurige discussie in Oracle-forums en nieuwsgroepen was de efficiëntie van het gebruik van count(*) om een rijtelling uit een bepaalde tabel te retourneren. Een nieuwe rimpel in die discussie introduceert nu count(rowid) als een efficiënter alternatief; het argument stelt dat count(*) de hele kolomlijst uitbreidt, net zoals "select * ...", en als zodanig een resource-sink zou kunnen zijn wanneer CLOB-kolommen aanwezig zijn in de gewenste tabel. Laten we naar dat argument kijken en kijken of het klopt. Laten we beginnen met het maken en vullen van een tabel met een CLOB-kolom:
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>
Laten we vervolgens gebeurtenis 10053 instellen om de optimalisatie-tracering te dumpen, zodat we kunnen zien hoe Oracle van plan is de count()-query's uit te voeren:
SQL> alter session set events = '10053 trace name context forever, level 2'; Session altered.
De fase is ingesteld, laten we enkele varianten van count() uitvoeren om te zien hoe Oracle zich gedraagt. Eerst voeren we een rechte telling(*) uit en geven we het plan weer:
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>
Kijkend naar het gegenereerde traceringsbestand gebruikt Oracle eenvoudig count(*) zoals het is om de resultaten te retourneren:
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>
Geen verrassingen daar; merk op dat Oracle de "*" niet uitbreidt naar alle kolommen in de tabel - de "*" geeft in dit geval aan dat alle rijen moeten worden geteld. Als er een echte kolomnaam was opgegeven, zou Oracle de waarden in de opgegeven kolom hebben geteld. Laten we nu eens kijken naar wat Oracle doet met een count(rowid)-query:
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 genereert een rowid-waarde voor elke rij in de tabel, een bewerking die enkele CPU-bronnen zal verbruiken. Aangezien de zoekopdracht in ongeveer dezelfde tijd als de count(*)-versie is geretourneerd, lijkt de prestatie 'hit' te verwaarlozen. Het toevoegen van een primaire sleutel verandert de plannen enigszins, maar niet de vraagtekst:
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;
De traceringsdetails van 10053 zijn niet gewijzigd nadat de primaire sleutel is toegevoegd.
Het lijkt erop dat uit dit experiment twee stukjes informatie zijn verkregen - count(rowid) is niet beter dan count(*) wanneer tabellen CLOB-kolommen bevatten en dat count(*) de kolomlijst niet uitbreidt zoals "select *" doet (en er is geen reden om aan te nemen dat dit zou moeten).
Het bewijs zit in de pudding, zoals het oude gezegde luidt.
# # #
Zie artikelen van David Fitzjarrell