Od dawna dyskusja na forach i grupach dyskusyjnych Oracle dotyczy efektywności użycia count(*) do zwracania liczby wierszy z danej tabeli. Nowa zmarszczka w tej dyskusji wprowadza teraz count(rowid) jako bardziej wydajną alternatywę; argument stwierdza, że count(*) rozszerza całą listę kolumn, podobnie jak „select * …” i jako taki może być ujściem zasobów, gdy kolumny CLOB są obecne w żądanej tabeli. Przyjrzyjmy się temu argumentowi i zobaczmy, czy ma sens. Zacznijmy od utworzenia i wypełnienia tabeli zawierającej kolumnę 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>
Następnie ustawmy zdarzenie 10053, aby zrzucić ślad optymalizatora, abyśmy mogli zobaczyć, jak Oracle planuje wykonać zapytania count():
SQL> alter session set events = '10053 trace name context forever, level 2'; Session altered.
Scena jest gotowa, uruchommy kilka wariantów count(), aby zobaczyć, jak zachowuje się Oracle. Najpierw wykonamy proste liczenie(*) i wyświetlimy 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>
Patrząc na wygenerowany plik śledzenia, Oracle po prostu używa count(*) bez zmian, aby zwrócić wyniki:
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>
Żadnych niespodzianek; zauważ, że Oracle nie rozszerza „*” na wszystkie kolumny w tabeli — „*” w tym przypadku oznacza, że mają być liczone wszystkie wiersze. Gdyby podano rzeczywistą nazwę kolumny, Oracle zliczyłoby wartości w określonej kolumnie. Przyjrzyjmy się teraz, co Oracle robi z zapytaniem 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 generuje wartość rowid dla każdego wiersza w tabeli, operacja, która zużywa część zasobów procesora. Ponieważ zapytanie powróciło mniej więcej w tym samym czasie, co wersja count(*), „trafienie” wydajności wydaje się być znikome. Dodanie klucza podstawowego nieznacznie zmienia plany, ale nie tekst zapytania:
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;
Szczegóły śledzenia 10053 nie zmieniły się po dodaniu klucza podstawowego.
Wygląda na to, że w tym eksperymencie zebrano dwie informacje — count(rowid) nie jest lepsza niż count(*), gdy tabele zawierają kolumny CLOB, a licznik(*) nie rozszerza listy kolumn, tak jak robi to „wybierz *”. (i nie ma powodu sądzić, że powinno).
Dowód jest w budyniu, jak mówi stare przysłowie.
# # #
Zobacz artykuły Davida Fitzjarrella