The Dutch Prutser’s Blog

By: Harald van Breederode

  • Disclaimer

    The views expressed on this blog are my own and do not necessarily reflect the views of Oracle.
  • Subscribe

Explaining the number of Consistent Gets

Posted by Harald van Breederode on November 6, 2009

Last week I received an email from a friend, who wishes to remain anonymous, with the question why Oracle needed 8 consistent gets to perform a full table scan on a table where all the rows are stored in just one data block. There are several possibilities that can cause this and that is what this posting is all about: Explaining the number of Consistent Gets.

Verifying the claim

Let me first demonstrate that the claim made by my friend is indeed true. I start by setting the STATISTICS_LEVEL parameter to ALL to enable the collection of “Plan Execution Statistics” needed by DBMS_XPLAN to be able to report the number of consistent gets. Next I execute a query to fetch the data followed by a call to DBMS_XPLAN to get and format the execution plan.

SQL> alter session set statistics_level = all;

Session altered.

SQL> select * from foo;

        C1
----------
         1
....
....
        64

64 rows selected.

SQL> select * from table(dbms_xplan.display_cursor( -
> format=>'basic -rows iostats last -rows'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select * from foo

Plan hash value: 1245013993

---------------------------------------------------------------------------
| Id  | Operation         | Name | Starts | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |      1 |     64 |00:00:00.01 |       8 |
|   1 |  TABLE ACCESS FULL| FOO  |      1 |     64 |00:00:00.01 |       8 |
---------------------------------------------------------------------------

We can clearly see that there are 64 rows in table FOO and that Oracle performed 8 consistent gets to fulfill my request for data. The question is why 8 and not something less assuming all 64 rows are indeed stored in the same data block. Making assumptions is risky, so, lets verify the one data block assumption:

SQL> select blocks from dba_tables where table_name = 'FOO';

    BLOCKS
----------
         1

SQL> select count(distinct dbms_rowid.rowid_block_number(rowid)) from foo;

COUNT(DISTINCTDBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID))
---------------------------------------------------
                                                  1

The above shows that according to the data dictionary all data is indeed stored in one data block, but this information could be stale. Hence the verification on the actual data itself. We now know for sure that all rows are indeed stored in one data block. But there is more to a table than data blocks and maybe that is causing the somewhat high number of consistent gets we saw earlier. I recall from memory that Oracle needs 3 consistent gets to perform a full table scan on an empty table, but before jumping to conclusions I better verify my memory”.

SQL> create table bar as select * from foo where 1=0;

Table created.

SQL> select * from bar;

no rows selected

SQL> select * from table(dbms_xplan.display_cursor( -
> format=>'basic -rows iostats last -rows'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select * from bar

Plan hash value: 4224476444

---------------------------------------------------------------------------
| Id  | Operation         | Name | Starts | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |      1 |      0 |00:00:00.01 |       3 |
|   1 |  TABLE ACCESS FULL| BAR  |      1 |      0 |00:00:00.01 |       3 |
---------------------------------------------------------------------------

The above confirms that Oracle indeed used three consistent gets to scan the empty table. This leaves us with 5 consistent gets to fetch the 64 rows. The question remains why 5 gets?

The arraysize

When a database client executes a query it starts fetching rows until there is nothing more to fetch. This fetching is performed in batches and the number of rows to fetch in one batch is known as the arraysize or fetchsize depending on the programming environment. The arraysize can be displayed in SQL*Plus using the ’show arraysize’ command as shown below:

SQL> show array
arraysize 15

By default SQL*Plus uses an arraysize of 15, which means that each fetch performed requests a batch of 15 rows. Thus if we are about to fetch 64 rows we need 5 batches. This is the key to the answer why Oracle needed 8 consistent gets to scan the 64 row table, 5 are used to fetch the actual rows and the other 3 were already shown.

We can easily show the effect of arraysize by raising it from 15 to 35 using the ’set arraysize’ command. This will reduce the number of batches from 5 to 2, resulting in a total of 5 consistent gets as demonstrated below:.

SQL> set array 35
SQL> select * from foo;

        C1
----------
         1
....
....
        64

64 rows selected.

SQL> select * from table(dbms_xplan.display_cursor( -
> format=>'basic -rows iostats last -rows'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select * from foo

Plan hash value: 1245013993

---------------------------------------------------------------------------
| Id  | Operation         | Name | Starts | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |      1 |     64 |00:00:00.01 |       5 |
|   1 |  TABLE ACCESS FULL| FOO  |      1 |     64 |00:00:00.01 |       5 |
---------------------------------------------------------------------------

If we double the arraysize from 35 to 70 all rows will be fetched in just one batch resulting in the lowest possible number of consistent gets (in this example).

SQL> set array 70
SQL> select * from foo;

        C1
----------
         1
....
....
        64

64 rows selected.

SQL> select * from table(dbms_xplan.display_cursor( -
> format=>'basic -rows iostats last -rows'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select * from foo

Plan hash value: 1245013993

---------------------------------------------------------------------------
| Id  | Operation         | Name | Starts | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |      1 |     64 |00:00:00.01 |       4 |
|   1 |  TABLE ACCESS FULL| FOO  |      1 |     64 |00:00:00.01 |       4 |
---------------------------------------------------------------------------

The arraysize is quite important when it comes to performance tuning and a good discussion about it can be found in Making Friends written by fellow OakTable member Cary Millsap.

The above answered the question from my friend. Before wrapping up there are a few things to watch out for when it comes to explaining the number of consistent gets.

The aggregate trap

The first thing to be aware of is what I call the “aggregate trap”. The arraysize controls the batch size between the Oracle database server and its client, and does not affect SQL operations which are running completely inside the server. The classic example is an SQL aggregation function as shown below:

SQL> select sum(c1) from foo;

   SUM(C1)
----------
      2080

SQL> select * from table(dbms_xplan.display_cursor( -
> format=>'basic -rows iostats last -rows'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select sum(c1) from foo

Plan hash value: 1342139204

----------------------------------------------------------------------------
| Id  | Operation          | Name | Starts | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |      1 |      1 |00:00:00.01 |       3 |
|   1 |  SORT AGGREGATE    |      |      1 |      1 |00:00:00.01 |       3 |
|   2 |   TABLE ACCESS FULL| FOO  |      1 |     64 |00:00:00.01 |       3 |
----------------------------------------------------------------------------

Although Oracle performed a full table scan on the 64 row table it only needed 3 consistent gets. Because the SUM function ran entirely inside the Oracle server process, which required no interaction with the client, the arraysize has no impact.

The Read Consistency Trap

Another thing to watch out for is known as the Read Consistency Trap, which causes the number of consistent gets to go up whenever the Oracle database server has to apply undo records to a data block to make it read consistent. Each undo record that gets applied increases the number of consistent gets by one as illustrated below:

SQL> show array
arraysize 70
SQL> select * from foo;

        C1
----------
         1
....
....
        64

64 rows selected.

SQL> select * from table(dbms_xplan.display_cursor( -
> format=>'basic -rows iostats last -rows'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select * from foo

Plan hash value: 1245013993

---------------------------------------------------------------------------
| Id  | Operation         | Name | Starts | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |      1 |     64 |00:00:00.01 |      69 |
|   1 |  TABLE ACCESS FULL| FOO  |      1 |     64 |00:00:00.01 |      69 |
---------------------------------------------------------------------------

Suddenly there are 69 consistent gets instead of 5, as shown previously, using an arraysize of 70. The extra 64 consistent gets are caused by applying 64 undo records to make the data block read consistent. The 64 undo records were created in another session by updating each row in a PL/SQL loop (one row at a time) without issuing a COMMIT.

A little quiz to wrap-up

The final example shows yet another number of consistent gets for retrieving the same 64 rows using an arraysize of 70:

SQL> show array
arraysize 70
SQL> select * from foo;

        C1
----------
         1
....
....
        64

64 rows selected.

SQL> select * from table(dbms_xplan.display_cursor( -
> format=>'basic -rows iostats last -rows'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
select * from foo

Plan hash value: 1245013993

---------------------------------------------------------------------------
| Id  | Operation         | Name | Starts | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |      1 |     64 |00:00:00.01 |      24 |
|   1 |  TABLE ACCESS FULL| FOO  |      1 |     64 |00:00:00.01 |      24 |
---------------------------------------------------------------------------

If you can explain why there are 24 consistent gets instead of just 5 please leave a comment below and I will let you know if you are correct ;-)
-Harald

Posted in Oracle | Leave a Comment »

Checkpoint presentation presented at the RAC SIG

Posted by Harald van Breederode on October 30, 2009

Yesterday I presented my What’s the Point of Oracle Checkpoints presentation at the Oracle RAC SIG and although I was a bit nervous it went quite well. However somehow my assigned time slot was shortened without notice and instead of having 90 minutes for my presentation I had to cut short after just 60 minutes. Lucky enough I finished the presentation before this, but there wasn’t enough time to run all my nicely prepared demos….

Yesterday evening I captured the demo output to give you a chance to look at the demos you had to miss yesterday. Apologies for cutting short yesterday.
-Harald

Posted in Oracle | 3 Comments »

Alive and Prutsing

Posted by Harald van Breederode on October 6, 2009

As you might have noticed I haven’t been exactly busy posting new stories on my blog. The obvious reason for this is that I have been way too busy doing other things such as prutsing with Oracle11g Release 2. Two weeks ago I taught my first NF11g class, which includes all major new release 2 new features, and you can imagine that it took some time to prepare myself for this course.

meanwhile I did upload an article (in Dutch) about SQL Plan Managementto my “Papers / Presentations” page that I wrote for the September 2009 issue of the Dutch Optimize magazine.

On my “Upcoming Events” page, which I update every week, you can read that I will present the RAC version of my “What’s the point of Oracle Checkpoints” presentation on October 29 in the web seminar series of the Oracle RAC SIG.

Furthermore I will talk about the new Data Guard features in Oracle11g Release 2 during the 4th Planboard DBA Symposium to be held on November 17.

Finally I will present my SQL Plan Management presentation on November 30 during the UKOUG Annual Conference in Birmingham.

Hopefully things are becoming a bit more quiet in the upcoming weeks so that I can find the time to write something about one or more interesting cool Oracle11g Release 2 new features.
-Harald

Posted in Oracle | Leave a Comment »

4th Planboard DBA Symposium: Registration now open

Posted by Harald van Breederode on September 27, 2009

On November 17 Planboard will run her 4th Dutch DBA Symposium and the registration is now open. This “for Dutch DBA’s, by Dutch DBA’s” symposium has become the place to be for the serious DBA who wants to share his or her knowledge with other DBA’s in an open environment with plenty of networking time between the 5 sessions. Please enroll your self on the Planboard website.
As a program committee member I recommend attending this wonderful event and I hope to see you on November 17.
-Harald

Posted in Linux, Oracle | Leave a Comment »

4th Planboard DBA Symposium: Call for Papers

Posted by Harald van Breederode on July 20, 2009

Planboard organizes her 4th Dutch DBA Symposium to be held on 17 November 2009 and the Call for Papers is now open. This "for DBA’s by DBA’s" symposium is in its second year and is starting to become a well known event in The Netherlands.

If you are a Dutch DBA who wants to share his or her experiences with other Dutch DBA’s you definitely want to be a speaker at this wonderful symposium! This is your chance to share that fantastic idea or solution you invented but didn’t knew where to share it.

Please visit the Planboard website and submit your abstract before 1 September 2009.
-Harald

Posted in Oracle | Leave a Comment »

Rolling Cursor Invalidation

Posted by Harald van Breederode on July 16, 2009

While working on a demo a while back I discovered the hard way that things do indeed change! Of course this is nothing new but by sharing my experience I hope to avoid others re-inventing the same wheel and to learn you something that you might not already know.

The demo I was working on is used to demonstrate how bind peeking works, the problems it can cause and how the Oracle11g Adaptive Cursor Sharing feature solves these problems. In this demo I call DBMS_STATS to create a histogram and I expected that dependent cursors would be marked INVALID afterwards but this simply didn’t happen. I almost lost my believe/confidence in myself and Oracle ;-)

Somehow I forgot, or maybe completely missed, the fact that cursors are invalidated in a rolling fashion since the introduction of Oracle10g. Before Oracle10g cursors were marked INVALID immediately by DBMS_STATS (or ANALYZE .. STATISTICS).

Before diving into the Oracle10g behavior, we start by looking at the pre-Oracle10g way of invalidating cursors.

Immediate Cursor Invalidation

Lets start by creating a table, gather object statistics on it, execute a query against it, grab its SQL_ID and take a look at the child cursor in the library cache:

SQL> select * from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - Production
PL/SQL Release 11.1.0.7.0 - Production
CORE    11.1.0.7.0      Production
TNS for Linux: Version 11.1.0.7.0 - Production
NLSRTL Version 11.1.0.7.0 - Production

SQL> create table foo as select * from dba_users;

Table created.

SQL> exec dbms_stats.gather_table_stats(null,'foo')

PL/SQL procedure successfully completed.

SQL> select count(*) from foo;

  COUNT(*)
----------
        13

SQL> select prev_sql_id sql_id
  2  from v$session where sid = sys_context('userenv', 'SID');

SQL_ID
-------------
5dkz0zau0v8yh

SQL> select child_number,executions,parse_calls,loads,invalidations
  2  from v$sql where sql_id = '5dkz0zau0v8yh';

CHILD_NUMBER EXECUTIONS PARSE_CALLS      LOADS INVALIDATIONS
------------ ---------- ----------- ---------- -------------
           0          1           1          1             0

The above shows that there is one child cursor in the library cache that has been hard parsed and executed once. See what happens if we execute the query again:

SQL> select count(*) from foo;

  COUNT(*)
----------
        13

SQL> select child_number,executions,parse_calls,loads,invalidations
  2  from v$sql where sql_id = '5dkz0zau0v8yh';

CHILD_NUMBER EXECUTIONS PARSE_CALLS      LOADS INVALIDATIONS
------------ ---------- ----------- ---------- -------------
           0          2           2          1             0

As shown above we see that the child cursor was soft parsed and executed again. The next step is to gather object statistics again using DBMS_STATS and let DBMS_STATS invalidate dependant cursors by setting the parameter NO_INVALIDATE to FALSE:

SQL> exec dbms_stats.gather_table_stats(null,'foo',no_invalidate => false)

PL/SQL procedure successfully completed.

SQL> select child_number,executions,parse_calls,loads,invalidations
  2  from v$sql where sql_id = '5dkz0zau0v8yh';

CHILD_NUMBER EXECUTIONS PARSE_CALLS      LOADS INVALIDATIONS
------------ ---------- ----------- ---------- -------------
           0          2           2          1             1

Looking at the child cursor we see that it has been marked INVALID but hasn’t been parsed again. The parse will be performed on a next execution as shown below:

SQL> select count(*) from foo;

  COUNT(*)
----------
        13

SQL> select child_number,executions,parse_calls,loads,invalidations
  2  from v$sql where sql_id = '5dkz0zau0v8yh';

CHILD_NUMBER EXECUTIONS PARSE_CALLS      LOADS INVALIDATIONS
------------ ---------- ----------- ---------- -------------
           0          1           1          2             1

Based on the output above we can conclude that the child cursor has been hard parsed again, after being marked INVALID, and that this new incarnation of the child cursor has been executed once. All of the above will also happen if you still use the deprecated ANALYZE .. STATISTICS command instead of DBMS_STATS.

SQL> analyze table foo compute statistics;

Table analyzed.

SQL> select child_number,executions,parse_calls,loads,invalidations
  2  from v$sql where sql_id = '5dkz0zau0v8yh';

CHILD_NUMBER EXECUTIONS PARSE_CALLS      LOADS INVALIDATIONS
------------ ---------- ----------- ---------- -------------
           0          1           1          2             2

Again we see that the child cursor has been marked INVALID but hasn’t been parsed again. The parse will occur on the next execution:

SQL> select count(*) from foo;

  COUNT(*)
----------
        13

SQL> select child_number,executions,parse_calls,loads,invalidations
  2  from v$sql where sql_id = '5dkz0zau0v8yh';

CHILD_NUMBER EXECUTIONS PARSE_CALLS      LOADS INVALIDATIONS
------------ ---------- ----------- ---------- -------------
           0          1           1          3             2

Like we concluded in the case of DBMS_STATS, we can conclude that the child cursor has been hard parsed again and re-executed after being marked INVALID.

This behavior was what I expected while working on my Oracle11g demo.

Now that we have a basic understanding of how things worked in the past, it is time to take a look at how cursor invalidations work in more recent versions of Oracle.

Rolling Cursor Invalidation

Starting with Oracle10g cursors are marked for rolling invalidation instead of marked INVALID immediately. On the next execution of the query the Oracle server will generate a random number between 0 and the value of the _optimizer_invalidation_period parameter, which has a default value of 18000, and the cursor will remain valid for this number of seconds. Upon every following execution Oracle will check if this random selected timeout has expired. If that is the case then the cursor will be hard parsed again. Before demonstrating rolling cursor invalidation, we’ll lower the value of _optimizer_invalidation_period in order to avoid long waiting times:

SQL> alter system set "_optimizer_invalidation_period" = 300 scope=memory;

System altered.

SQL> exec dbms_stats.gather_table_stats(null,'foo',no_invalidate => DBMS_STATS.AUTO_INVALIDATE)

PL/SQL procedure successfully completed.

SQL> select child_number,executions,parse_calls,loads,invalidations
  2  from v$sql where sql_id = '5dkz0zau0v8yh';

CHILD_NUMBER EXECUTIONS PARSE_CALLS      LOADS INVALIDATIONS
------------ ---------- ----------- ---------- -------------
           0          1           1          3             2

Instead of marking the child cursor INVALID immediately, it is now marked for rolling invalidation behind the scenes. Nothing will happen with respect to cursor invalidation unless we execute the query again:

SQL> select count(*) from foo;

  COUNT(*)
----------
        13

SQL> select child_number,executions,parse_calls,loads,invalidations
  2  from v$sql where sql_id = '5dkz0zau0v8yh';

CHILD_NUMBER EXECUTIONS PARSE_CALLS      LOADS INVALIDATIONS
------------ ---------- ----------- ---------- -------------
           0          2           2          3             2

We can’t look behind the scenes but the random invalidation timeout has been generated and the child cursor will remain valid. The query will be executed without a parse as long as this timeout hasn’t been expired. In order to see what happens when the timeout is over we have to wait at least _optimizer_invalidation_period seconds.:

SQL> !sleep 300

SQL> select child_number,executions,parse_calls,loads,invalidations
  2  from v$sql where sql_id = '5dkz0zau0v8yh';

CHILD_NUMBER EXECUTIONS PARSE_CALLS      LOADS INVALIDATIONS
------------ ---------- ----------- ---------- -------------
           0          2           2          3             2

Despite the fact that we know the timeout has expired the child cursor is not marked INVALID and as a matter of fact it won’t at all. Lets see what happens when we execute the query again now that the timeout is over:

SQL> select count(*) from foo;

  COUNT(*)
----------
        13

SQL> select child_number,executions,parse_calls,loads,invalidations
  2  from v$sql where sql_id = '5dkz0zau0v8yh';

CHILD_NUMBER EXECUTIONS PARSE_CALLS      LOADS INVALIDATIONS
------------ ---------- ----------- ---------- -------------
           0          2           2          3             2
           1          1           1          1             0

Instead of hard parsing the child cursor again as shown before, we now see another child cursor showing up in the library cache. This new child cursor was hard parsed and executed once. The reason for this new child can be found in V$SQL_SHARED_CURSOR as shown below:

SQL> select child_number,ROLL_INVALID_MISMATCH from v$sql_shared_cursor where sql_id = '5dkz0zau0v8yh';

CHILD_NUMBER R
------------ -
           0 N
           1 Y

This indicates that the new child was born because the previous child had a mismatch due to a rolling cursor invalidation.

Before finishing this blog post we need to set the _optimizer_invalidation_period parameter back to its default value:

SQL> alter system set "_optimizer_invalidation_period" = 18000 scope=memory;

System altered.

Conclusion

Things will change without notice! ;-) Cursor invalidation has changed in the past and the reason for this change is that previously all
dependent cursors were invalidated immediately after gathering new object statistics. This massive invalidation might cause a serious
degradation in performance right after statistics gathering due to a high number of hard parses. Rolling cursor invalidation spreads the invalidation of cursors out over a longer period of time thereby avoiding the performance
degradation caused by hard parsing the invalidated cursors.

Reference: Metalink note:557661.1
-Harald

Posted in Oracle | 10 Comments »