Is COUNT(rowid) Faster Than COUNT(*)?

A long-standing discussion in Oracle forums and newsgroups has been the efficiency of using count(*) to return a rowcount from a given table. A new wrinkle in that discussion now introduces count(rowid) as a more efficient alternative; the argument states that count(*) expands the entire column list, much like “select * …”, and, as such, could be a resource sink when CLOB columns are present in the desired table. Let’s look at that argument and see if it holds water. Let’s start by creating and populating a table containing a CLOB column:


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>

Next let’s set event 10053 to dump the optimizer trace so we can see how Oracle plans to execute the count() queries:


SQL> alter session set events = '10053 trace  name context forever, level 2';

Session altered.

The stage is set, let’s run some variants of count() to see how Oracle behaves. First, we’ll execute a straight count(*) and display the 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>

Looking at the trace file generated Oracle simply uses count(*) as-is to return the results:


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 surprises there; notice that Oracle does not expand the “*” to all columns in the table — the “*” in this case indicates that all rows are to be counted. Had an actual column name been provided then Oracle would have counted values in the specified column. Let’s now look at what Oracle does with a 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 generates a rowid value for each row in the table, an operation which will consume some CPU resources. Since the query returned in roughly the same time as the count(*) version the performance ‘hit’ appears to be negligible. Adding a primary key changes the plans slightly but not the query text:



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;

The 10053 trace details did not change after the primary key was added.

It would appear that two pieces of information have been gleaned from this experiment — count(rowid) is no better than count(*) when tables contain CLOB columns and that count(*) does not expand the column list as “select *” does (and there is no reason to believe that it should).

The proof is in the pudding, as the old adage goes.

# # #

See articles by David Fitzjarrell

David Fitzjarrell
David Fitzjarrell has more than 20 years of administration experience with various releases of the Oracle DBMS. He has installed the Oracle software on many platforms, including UNIX, Windows and Linux, and monitored and tuned performance in those environments. He is knowledgeable in the traditional tools for performance tuning – the Oracle Wait Interface, Statspack, event 10046 and 10053 traces, tkprof, explain plan and autotrace – and has used these to great advantage at the U.S. Postal Service, American Airlines/SABRE, ConocoPhilips and SiriusXM Radio, among others, to increase throughput and improve the quality of the production system. He has also set up scripts to regularly monitor available space and set thresholds to notify DBAs of impending space shortages before they affect the production environment. These scripts generate data which can also used to trend database growth over time, aiding in capacity planning. He has used RMAN, Streams, RAC and Data Guard in Oracle installations to ensure full recoverability and failover capabilities as well as high availability, and has configured a 'cascading' set of DR databases using the primary DR databases as the source, managing the archivelog transfers manually and montoring, through scripts, the health of these secondary DR databases. He has also used ASM, ASMM and ASSM to improve performance and manage storage and shared memory.

Latest Articles