Friday, August 30, 2013

Cardinality Feedback

Cardinality Feedback  (CFB) is  a  feature of the Oracle Optimizer for correcting cardinality estimation (i.e., number of rows returned by each operation) in execution plans. Rather than going to endless complications to determine the right cardinality, we just wait for the result of each step in the execution plan, store it in the shared pool and reference it on subsequent executions, in the hope that the information will give us a good idea of how well  we did the last time.

CFB is a feature of Oracle 11gR2. For more details read the following post.

In this post, we will show CFB in action.
We will run a query (with CFB enabled) for which the optimizer fails (to a great extent) to estimate the cardinality of certain operations. This results in a suboptimal plan chosen by the optimizer. The next time we run the query, the optimizer uses CFB to improve its estimation and generates another (hopefully better) plan. This continues for the next executions until finally the plan stabilizes. Then we flush the shared pool (in order to wipe out all the relevant cursors for this query) and we disable the CFB feature and rerun the query. This time we see that the plan does not change, no matter how many times we execute the query. Lets see it in action:

This is our query:

------------------------------------------------------------------------------------------------------------

nikos@NIKOSDB> get mysql2
  1  SELECT /*+ gather_plan_statistics  */ ch.channel_class, c.cust_city, t.calendar_quarter_desc,
  2     SUM(s.amount_sold) sales_amount
  3  FROM sales s, times t, customers c, channels ch
  4  WHERE s.time_id = t.time_id
  5  AND   s.cust_id = c.cust_id
  6  AND   s.channel_id = ch.channel_id
  7  AND   c.cust_state_province = 'CA'
  8  AND   ch.channel_desc in ('Internet','Catalog')
  9  AND   t.calendar_quarter_desc IN ('1999-01','1999-02')
 10* GROUP BY ch.channel_class, c.cust_city, t.calendar_quarter_desc
nikos@NIKOSDB> /

CHANNEL_CLASS        CUST_CITY                      CALENDA SALES_AMOUNT
-------------------- ------------------------------ ------- ------------
Indirect             Arbuckle                       1999-02        241.2
Indirect             Quartzhill                     1999-01        987.3
Indirect             Legrand                        1999-02        18.66
Indirect             Los Angeles                    1999-01      1886.19
Indirect             Cloverdale                     1999-02       266.28
Indirect             Pescadero                      1999-02       298.44
Indirect             Pescadero                      1999-01        26.32
Indirect             Legrand                        1999-01        26.32
Indirect             Arbuckle                       1999-01       270.08
Indirect             Montara                        1999-01       289.07
Indirect             Los Angeles                    1999-02      2128.59
Indirect             San Mateo                      1999-01      8754.59
Indirect             El Sobrante                    1999-02      3744.03
Indirect             Pala                           1999-02       936.62
Indirect             El Sobrante                    1999-01      5392.34
Indirect             San Francisco                  1999-02        11257
Indirect             Cloverdale                     1999-01        52.64
Indirect             Montara                        1999-02      1618.01
Indirect             San Mateo                      1999-02     21399.42
Indirect             San Francisco                  1999-01      3058.27
Indirect             Pala                           1999-01      3263.93
Indirect             Quartzhill                     1999-02       412.83

22 rows selected.

Elapsed: 00:00:00.27
nikos@NIKOSDB> @xplan
Enter value for sql_id:
Enter value for child_number:

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------
SQL_ID  8rhhgss531vn9, child number 0
-------------------------------------
SELECT /*+ gather_plan_statistics  */ ch.channel_class, c.cust_city,
t.calendar_quarter_desc,    SUM(s.amount_sold) sales_amount FROM sales
s, times t, customers c, channels ch WHERE s.time_id = t.time_id AND
s.cust_id = c.cust_id AND   s.channel_id = ch.channel_id AND
c.cust_state_province = 'CA' AND   ch.channel_desc in
('Internet','Catalog') AND   t.calendar_quarter_desc IN
('1999-01','1999-02') GROUP BY ch.channel_class, c.cust_city,
t.calendar_quarter_desc

Plan hash value: 2264303152

--------------------------------------------------------------------------------------------------------
| Id  | Operation                               | Name                      | Starts | E-Rows | A-Rows |
--------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                        |                           |      1 |        |     22 |
|   1 |  TEMP TABLE TRANSFORMATION              |                           |      1 |        |     22 |
|   2 |   LOAD AS SELECT                        |                           |      1 |        |      0 |
|*  3 |    TABLE ACCESS FULL                    | CUSTOMERS                 |      1 |   3060 |   3341 |
|   4 |   HASH GROUP BY                         |                           |      1 |   1241 |     22 |
|*  5 |    HASH JOIN                            |                           |      1 |   2009 |    964 |
|*  6 |     TABLE ACCESS FULL                   | TIMES                     |      1 |    181 |    181 |
|*  7 |     HASH JOIN                           |                           |      1 |   2009 |    964 |
|*  8 |      TABLE ACCESS FULL                  | CHANNELS                  |      1 |      2 |      2 |
|*  9 |      HASH JOIN                          |                           |      1 |   2009 |    964 |
|  10 |       PARTITION RANGE SUBQUERY          |                           |      1 |   2008 |    964 |
|  11 |        TABLE ACCESS BY LOCAL INDEX ROWID| SALES                     |      2 |   2008 |    964 |
|  12 |         BITMAP CONVERSION TO ROWIDS     |                           |      2 |        |    964 |
|  13 |          BITMAP AND                     |                           |      2 |        |      2 |
|  14 |           BITMAP MERGE                  |                           |      2 |        |      2 |
|  15 |            BITMAP KEY ITERATION         |                           |      2 |        |      2 |
|  16 |             BUFFER SORT                 |                           |      2 |        |      4 |
|* 17 |              TABLE ACCESS FULL          | CHANNELS                  |      1 |      2 |      2 |
|* 18 |             BITMAP INDEX RANGE SCAN     | SALES_CHANNEL_BIX         |      4 |        |      2 |
|  19 |           BITMAP MERGE                  |                           |      2 |        |      2 |
|  20 |            BITMAP KEY ITERATION         |                           |      2 |        |    181 |
|  21 |             BUFFER SORT                 |                           |      2 |        |    362 |
|* 22 |              TABLE ACCESS FULL          | TIMES                     |      1 |    181 |    181 |
|* 23 |             BITMAP INDEX RANGE SCAN     | SALES_TIME_BIX            |    362 |        |    181 |
|  24 |           BITMAP MERGE                  |                           |      2 |        |      2 |
|  25 |            BITMAP KEY ITERATION         |                           |      2 |        |    403 |
|  26 |             BUFFER SORT                 |                           |      2 |        |   6682 |
|  27 |              TABLE ACCESS FULL          | SYS_TEMP_0FD9D66B5_C5E4FF |      1 |   3060 |   3341 |
|* 28 |             BITMAP INDEX RANGE SCAN     | SALES_CUST_BIX            |   6682 |        |    403 |
|  29 |       TABLE ACCESS FULL                 | SYS_TEMP_0FD9D66B5_C5E4FF |      1 |   3060 |   3341 |
--------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - filter("C"."CUST_STATE_PROVINCE"='CA')
   5 - access("S"."TIME_ID"="T"."TIME_ID")
   6 - filter(("T"."CALENDAR_QUARTER_DESC"='1999-01' OR "T"."CALENDAR_QUARTER_DESC"='1999-02'))
   7 - access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
   8 - filter(("CH"."CHANNEL_DESC"='Catalog' OR "CH"."CHANNEL_DESC"='Internet'))
   9 - access("S"."CUST_ID"="C0")
  17 - filter(("CH"."CHANNEL_DESC"='Catalog' OR "CH"."CHANNEL_DESC"='Internet'))
  18 - access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
  22 - filter(("T"."CALENDAR_QUARTER_DESC"='1999-01' OR "T"."CALENDAR_QUARTER_DESC"='1999-02'))
  23 - access("S"."TIME_ID"="T"."TIME_ID")
  28 - access("S"."CUST_ID"="C0")

Note
-----
   - star transformation used for this statement


67 rows selected.

Elapsed: 00:00:00.32
------------------------------------------------------------------------------------------------------------
We have run the query and we show above the execution plan. If you check the estimated rows (E-Rows) versus the actual rows (A-Rows) it is obvious that for certain operations (e.g., for the group by operation (4)) the cardinality estimation is by far wrong. This means that the plan (plan hash value 2264303152)  probably is not the best one. This calls for CFB to kick in. Lets rerun the query.
------------------------------------------------------------------------------------------------------------

nikos@NIKOSDB> get mysql2
  1  SELECT /*+ gather_plan_statistics  */ ch.channel_class, c.cust_city, t.calendar_quarter_de
  2     SUM(s.amount_sold) sales_amount
  3  FROM sales s, times t, customers c, channels ch
  4  WHERE s.time_id = t.time_id
  5  AND   s.cust_id = c.cust_id
  6  AND   s.channel_id = ch.channel_id
  7  AND   c.cust_state_province = 'CA'
  8  AND   ch.channel_desc in ('Internet','Catalog')
  9  AND   t.calendar_quarter_desc IN ('1999-01','1999-02')
 10* GROUP BY ch.channel_class, c.cust_city, t.calendar_quarter_desc
nikos@NIKOSDB> /

CHANNEL_CLASS        CUST_CITY                      CALENDA SALES_AMOUNT
-------------------- ------------------------------ ------- ------------
Indirect             Arbuckle                       1999-02        241.2
Indirect             Quartzhill                     1999-01        987.3
Indirect             Legrand                        1999-02        18.66
Indirect             Los Angeles                    1999-01      1886.19
Indirect             Cloverdale                     1999-02       266.28
Indirect             Pescadero                      1999-02       298.44
Indirect             Pescadero                      1999-01        26.32
Indirect             Legrand                        1999-01        26.32
Indirect             Arbuckle                       1999-01       270.08
Indirect             Montara                        1999-01       289.07
Indirect             Los Angeles                    1999-02      2128.59
Indirect             San Mateo                      1999-01      8754.59
Indirect             El Sobrante                    1999-02      3744.03
Indirect             Pala                           1999-02       936.62
Indirect             El Sobrante                    1999-01      5392.34
Indirect             San Francisco                  1999-02        11257
Indirect             Cloverdale                     1999-01        52.64
Indirect             Montara                        1999-02      1618.01
Indirect             San Mateo                      1999-02     21399.42
Indirect             San Francisco                  1999-01      3058.27
Indirect             Pala                           1999-01      3263.93
Indirect             Quartzhill                     1999-02       412.83

22 rows selected.

Elapsed: 00:00:00.12
nikos@NIKOSDB> @xplan
Enter value for sql_id:
Enter value for child_number:

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------
SQL_ID  8rhhgss531vn9, child number 1
-------------------------------------
SELECT /*+ gather_plan_statistics  */ ch.channel_class, c.cust_city,
t.calendar_quarter_desc,    SUM(s.amount_sold) sales_amount FROM sales
s, times t, customers c, channels ch WHERE s.time_id = t.time_id AND
s.cust_id = c.cust_id AND   s.channel_id = ch.channel_id AND
c.cust_state_province = 'CA' AND   ch.channel_desc in
('Internet','Catalog') AND   t.calendar_quarter_desc IN
('1999-01','1999-02') GROUP BY ch.channel_class, c.cust_city,
t.calendar_quarter_desc

Plan hash value: 798384265

-----------------------------------------------------------------------------------------------
| Id  | Operation                              | Name              | Starts | E-Rows | A-Rows |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |                   |      1 |        |     22 |
|   1 |  HASH GROUP BY                         |                   |      1 |     38 |     22 |
|*  2 |   HASH JOIN                            |                   |      1 |     38 |    964 |
|*  3 |    HASH JOIN                           |                   |      1 |     38 |  14296 |
|*  4 |     HASH JOIN                          |                   |      1 |     38 |  14296 |
|*  5 |      TABLE ACCESS FULL                 | CHANNELS          |      1 |      2 |      2 |
|   6 |      PARTITION RANGE SUBQUERY          |                   |      1 |     38 |  14296 |
|   7 |       TABLE ACCESS BY LOCAL INDEX ROWID| SALES             |      2 |     38 |  14296 |
|   8 |        BITMAP CONVERSION TO ROWIDS     |                   |      2 |        |  14296 |
|   9 |         BITMAP AND                     |                   |      2 |        |      2 |
|  10 |          BITMAP MERGE                  |                   |      2 |        |      2 |
|  11 |           BITMAP KEY ITERATION         |                   |      2 |        |      2 |
|  12 |            BUFFER SORT                 |                   |      2 |        |      4 |
|* 13 |             TABLE ACCESS FULL          | CHANNELS          |      1 |      2 |      2 |
|* 14 |            BITMAP INDEX RANGE SCAN     | SALES_CHANNEL_BIX |      4 |        |      2 |
|  15 |          BITMAP MERGE                  |                   |      2 |        |      2 |
|  16 |           BITMAP KEY ITERATION         |                   |      2 |        |    181 |
|  17 |            BUFFER SORT                 |                   |      2 |        |    362 |
|* 18 |             TABLE ACCESS FULL          | TIMES             |      1 |    181 |    181 |
|* 19 |            BITMAP INDEX RANGE SCAN     | SALES_TIME_BIX    |    362 |        |    181 |
|* 20 |     TABLE ACCESS FULL                  | TIMES             |      1 |    181 |    181 |
|* 21 |    TABLE ACCESS FULL                   | CUSTOMERS         |      1 |   3060 |   3341 |
-----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("S"."CUST_ID"="C"."CUST_ID")
   3 - access("S"."TIME_ID"="T"."TIME_ID")
   4 - access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
   5 - filter(("CH"."CHANNEL_DESC"='Catalog' OR "CH"."CHANNEL_DESC"='Internet'))
  13 - filter(("CH"."CHANNEL_DESC"='Catalog' OR "CH"."CHANNEL_DESC"='Internet'))
  14 - access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
  18 - filter(("T"."CALENDAR_QUARTER_DESC"='1999-01' OR "T"."CALENDAR_QUARTER_DESC"='1999-02'))
  19 - access("S"."TIME_ID"="T"."TIME_ID")
  20 - filter(("T"."CALENDAR_QUARTER_DESC"='1999-01' OR "T"."CALENDAR_QUARTER_DESC"='1999-02'))
  21 - filter("C"."CUST_STATE_PROVINCE"='CA')

Note
-----
   - star transformation used for this statement
   - cardinality feedback used for this statement


59 rows selected.

Elapsed: 00:00:00.22
------------------------------------------------------------------------------------------------------------
Check out the plan after the second run. We see that a new child cursor has been created (child number 1) and a new execution plan (corresponding to a new plan hash value). This time the estimated rows for the grouping operation (1)  has been greatly improved (E-Rows = 38 versus A-Rows = 22). However there are other operations with a great gap between estimated and actual. This means that CFB will continue its work in the next executions also. Also see at the "Note" section of the plan that it is explicitly stated that cardinality feedback has been used by the optimizer.

We will continue to run the query several times. Each time we run the query and CFB kicks in, a new child cursor is created with a new plan. The old cursors are marked as "not shareable", which means that are garbage waiting to be aged out from the shared pool with the next garbage collection.

Lets see the corresponding cursors for the SQL_ID after we have run the query for 10 more times.
------------------------------------------------------------------------------------------------------------

nikos@NIKOSDB> @fs
Enter value for sql_text:
Enter value for sql_id: 8rhhgss531vn9

     PARSING_SCHEMA_NAME       SQL_ID         CHILD  PLAN_HASH      EXECS     AVG_ETIME      AVG_LIO IS_SHAREABLE
------------------------------ ------------- ------ ---------- ---------- ------------- ------------ ------------
SH                             8rhhgss531vn9      0 2264303152          1           .21       20,068 N







SH                             8rhhgss531vn9      1  798384265          1           .07        2,641 N







SH                             8rhhgss531vn9      2 3691748271          1           .17       16,211 N







SH                             8rhhgss531vn9      3   54399499          1           .08       16,211 N







SH                             8rhhgss531vn9      4 4028491232          1           .07       16,211 N







SH                             8rhhgss531vn9      5 1742562322          7           .07       16,116 Y
------------------------------------------------------------------------------------------------------------

Each line corresponds to a different child cursor (the script uses V$SQL), which has  a different execution plan denoted by the PLAN_HASH column. The EXEC column denotes how many time this cursor has been executed. The column IS_SHAREABLE denotes if the cursor is shared ('Y') or not, which means that it is not used any more. From the first 5 rows we infer that it took 5 executions for the optimizer to stabilize the plan. And indeed it is the last row (child number 5) which relates to the final plan. So you see, CFB does not run forever. At some point it stabilizes to a specific plan.

Now, if we disable the CFB feature then we can see that for the same query, the optimizer from the very first execution, sticks with a single execution plan. We can disable cardinality feedback by setting the hidden parameter _OPTIMIZER_USE_FEEDBACK. Lets see it in action:

First we flush the shared pool in order to wipe out all the available cursors.
------------------------------------------------------------------------------------------------------------

sys@NIKOSDB> alter system flush shared_pool;

System altered.

nikos@NIKOSDB> alter session set "_optimizer_use_feedback"=FALSE;

Session altered.

-- We execute the query several (12) times
...

nikos@NIKOSDB> @fs
Enter value for sql_text:
Enter value for sql_id: 8rhhgss531vn9

     PARSING_SCHEMA_NAME       SQL_ID         CHILD  PLAN_HASH      EXECS     AVG_ETIME      AVG_LIO IS_SHAREABLE
------------------------------ ------------- ------ ---------- ---------- ------------- ------------ ------------
SH                             8rhhgss531vn9      0 4091383804         12           .08       16,431 Y



------------------------------------------------------------------------------------------------------------
We have disabled CFB and then  executed the query 12 times. We query V$SQL and this time we see only a single child cursor and only one execution plan. This is how CFB works!

Enjoy!
Oracle Learner 

No comments:

Post a Comment