Beiträge getaggt mit Performance Tuning
When is setting OPTIMIZER_INDEX_COST_ADJ appropriate?
The initialization parameter OPTIMIZER_INDEX_COST_ADJ has a certain popularity in the area of Oracle Database Performance Tuning. Especially, it can be used to make the Optimizer favor index access instead of full table scans. But that is not necessarily appropriate – in fact it can have very counterproductive effects on performance. Richard Foote has some very instructive postings about this, especially this one is impressive, in my view.
Reducing Buffer Busy Waits with Automatic Segment Space Management & Hash Partitioning
Last week, I was in Frankfurt (Germany) teaching about Performance Tuning & Partitioning in a Private Event for LHS Telecommunication. One demonstration that I developed for that event was about Reducing Buffer Busy Waits. One major reason why the wait event Buffer Busy Waits can occur is if many sessions are inserting at the same time in the same table. Large OLTP installations therefore will see it most likely. This can lead to a performance problem, especially if the site does not make use of Automatic Segment Space Management – a technique introduced in 9i and used by default since 10g. The old fashioned way to determine where to insert a new row in blocks before the high water mark uses Freelists. So if you are going with that „traditional“ technique with freelists (and PCTUSED), then your segment has one freelist by default, which leads to contention easy, because multiple simultaneous sessions that insert look at that one freelist and all pick the same Blocks to insert in. Here is an example:
SQL> create tablespace old datafile '/u01/app/oracle/oradata/orcl/old01.dbf'
size 50m segment space management manual;
Tablespace created.
SQL> create table bbw_freelist (n number, name varchar2(50)) tablespace old;
Table created.
That table now uses (one) freelist and PCTUSED (40 by default) in order to determine what blocks may be used
for insert. The next steps are going to create 10 simultaneous sessions that insert 1 Million rows into the table.
SQL> create or replace procedure bufwait1 as begin for i in 1..100000 loop insert into bbw_freelist values (i, 'BUFFER BUSY WAITS?'); end loop; commit; end; / 2 3 4 5 6 7 8 Procedure created. SQL> create or replace procedure do_bbw1 as v_jobno number:=0;begin FOR i in 1..10 LOOP dbms_job.submit(v_jobno,'bufwait1;', sysdate); END LOOP; commit; end; / 2 3 4 5 6 7 8 Procedure created.SQL> exec do_bbw1 PL/SQL procedure successfully completed.SQL> select count(*) from dba_jobs_running
COUNT(*)
----------
0
One easy way to discover Buffer Busy Waits is v$segment_statistics.Of course, you may see them also in Statspack/AWR reports.
SQL> select sum(value) from v$segment_statistics where OBJECT_NAME='BBW_FREELIST' and statistic_name='buffer busy waits'; SUM(VALUE) ---------- 1623Next we are doing exactly the same inserts with a table that uses Automatic Segment Space Management.
Simply speaking, ASSM achieves a better distribution of the new rows across multiple blocks if multiple
inserts occur at the same time. It is determined at the tablespace level:
SQL> select SEGMENT_SPACE_MANAGEMENT from dba_tablespaces where tablespace_name='USERS'; SEGMEN ------ AUTO SQL> create table bbw_assm (n number, name varchar2(50)) tablespace users; Table created. create or replace procedure bufwait2 as begin for i in 1..100000 loop insert into bbw_assm values (i, 'BUFFER BUSY WAITS?'); end loop; commit; end; / create or replace procedure do_bbw2 as v_jobno number:=0;begin FOR i in 1..10 LOOP dbms_job.submit(v_jobno,'bufwait2;', sysdate); END LOOP; commit; end; / SQL> exec do_bbw2 PL/SQL procedure successfully completed. SQL> select count(*) from dba_jobs_running; COUNT(*) ---------- 10 SQL> select count(*) from dba_jobs_running; COUNT(*) ---------- 0 SQL> select sum(value) from v$segment_statistics where OBJECT_NAME='BBW_ASSM' and statistic_name='buffer busy waits'; SUM(VALUE) ---------- 295As you can see, ASSM reduced strongly the number of Buffer Busy Waits! But still there are some,
and still this may be a performance problem for very large OLTP sites. Right now, the table is
not partitioned, so we have only one „hot extent“ if the table is continually growing, all inserts
occur in the newest allocated extent – where they are distributed nicely via ASSM. If that same table
would be Hash Partitioned, then we could have multiple „hot extents“ further more reducing the contention:
SQL> create table bbw_hash (n number, name varchar2(50))tablespace users partition by hash (n) partitions 256; Table created. SQL> create or replace procedure bufwait3 as begin for i in 1..100000 loop insert into bbw_hash values (i, 'BUFFER BUSY WAITS?'); end loop; commit; end; / 2 3 4 5 6 7 8 Procedure created. SQL> create or replace procedure do_bbw3 as v_jobno number:=0;begin FOR i in 1..10 LOOP dbms_job.submit(v_jobno,'bufwait3;', sysdate); END LOOP; commit; end; / 2 3 4 5 6 7 8 Procedure created. SQL> exec do_bbw3 PL/SQL procedure successfully completed. SQL> select sum(value) from v$segment_statistics where OBJECT_NAME='BBW_HASH' and statistic_name='buffer busy waits'; SUM(VALUE) ---------- 226We observe a (in this case) moderate further decrease of the Buffer Busy Waits. That
is one major reason for hash partitioning: Fighting against contention. Another reason
is – as in other kinds of partitioning – the possibilty for Partiton Pruning, if the
partition key is part of the where-clause of our statement. Then the optimizer
implicitly knows what partitions to scan. Even in my (relatively tiny) two tables, that
effect is visible:
SQL> set timing on SQL> select * from bbw_assm where n=4711; N NAME ---------- -------------------------------------------------- 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 10 rows selected. Elapsed: 00:00:00.11 SQL> select * from bbw_hash where n=4711; N NAME ---------- -------------------------------------------------- 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 4711 BUFFER BUSY WAITS? 10 rows selected. Elapsed: 00:00:00.01
Restrict the usage of new execution plans with SQL Plan Baselines
We have several new features in the 11g version related to change management. One of them is SQL Plan Baselining. That means that you intend a very conservative approach regarding the usage of new execution plans computed by the optimizer. In other words: You suspect that after a change, the newly computed execution plans may be bad and lead to a performance degradation.
Before the introduction of SQL Plan Baselines, you where able to fix the ‚good old execution plans‘ with hints or with stored outlines. Both have the disadvantage that they make it impossible for the optimizer to come up with even better execution plans than the ‚good old‘ ones. By using SQL Plan Baselines, you let the optimizer compute the new plans and you store them, with the possibility to later on test them and allow them for usage, if they have proved to be good. That process is called evolvement.
I have developed a little scenario to demonstrate that. First we see a statement with the ‚good old‘ execution plan of ACCESS BY INDEX ROWID:
SQL> select channel_id,count(*) from sales group by channel_id;
CHANNEL_ID COUNT(*)
---------- ----------
1 1
2 2064200
4 947328
3 4322623
9 16592
SQL> create bitmap index sales_bix on sales (channel_id) nologging;
Index created.
SQL> begin
dbms_stats.gather_table_stats('ADAM','SALES',cascade=>true,
method_opt=>'FOR ALL INDEXED COLUMNS SIZE 5');
end;
/
SQL> select sum(amount_sold) from sales where channel_id=1;
SUM(AMOUNT_SOLD)
----------------
1232.16
Execution Plan
----------------------------------------------------------
Plan hash value: 3388944652
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 8 | 1107 (0)| 00:00:14 |
| 1 | SORT AGGREGATE | | 1 | 8 | | |
| 2 | TABLE ACCESS BY INDEX ROWID | SALES | 5774 | 46192 | 1107 (0)| 00:00:14 |
| 3 | BITMAP CONVERSION TO ROWIDS| | | | | |
|* 4 | BITMAP INDEX SINGLE VALUE | SALES_BIX | | | |
-----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("CHANNEL_ID"=1)
Now I want to create a SQL Baseline for that statement with that execution plan.
One possibility to create it is the following:
SQL> show parameter baseline NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ optimizer_capture_sql_plan_baselines boolean FALSE optimizer_use_sql_plan_baselines boolean TRUE SQL> select * from dba_sql_plan_baselines; no rows selected SQL> alter session set optimizer_capture_sql_plan_baselines=true; Session altered.
The following (repeating) statements in this session are getting recorded together with their execution plans:
SQL> select sum(amount_sold) from sales where channel_id=1; SUM(AMOUNT_SOLD) ---------------- 1232.16 SQL> select sum(amount_sold) from sales where channel_id=1; SUM(AMOUNT_SOLD) ---------------- 1232.16 SQL> alter session set optimizer_capture_sql_plan_baselines=false; Session altered.
Following shows that there was one statement with one plan was recorded. This one plan
(the index access) is the only accepted one.
SQL> column sql_text format a29
SQL> select SQL_TEXT,SQL_HANDLE,PLAN_NAME,ENABLED,ACCEPTED,FIXED from dba_sql_plan_baselines;
SQL_TEXT SQL_HANDLE PLAN_NAME ENA ACC FIX
----------------------------- ------------------------------ ------------------------------ --- --- ---
select sum(amount_sold) from SYS_SQL_ec28978ecd0909c3 SYS_SQL_PLAN_cd0909c333c3099a YES YES NO
sales where channel_id=1
Now we change the conditions, so that a Full Table Scan becomes attractive to the optimizer:
SQL> alter table sales nologging;
SQL> insert /*+ append */ into sales select PROD_ID,CUST_ID,TIME_ID,1,PROMO_ID,QUANTITY_SOLD,AMOUNT_SOLD from sales;
7350744 rows created.
SQL> commit;
Commit complete.
SQL> delete from sales where channel_id=3;
4322623 rows deleted.
SQL> commit;
Commit complete.
SQL> alter table sales enable row movement;
Table altered.
SQL> alter table sales shrink space cascade;
Table altered.
SQL> begin dbms_stats.gather_table_stats('ADAM','SALES',cascade=>true,
method_opt=>'FOR ALL INDEXED COLUMNS SIZE 5');
end;
/
PL/SQL procedure successfully completed.
The majority of rows now has the channel_id 1. The optimizer would like to do FTS now, but the baseline restricts it to the usage of the ‚good old‘ index access:
SQL> set autotrace on explain
SQL> select sum(amount_sold) from sales where channel_id=1;
SUM(AMOUNT_SOLD)
----------------
785647882
Execution Plan
----------------------------------------------------------
Plan hash value: 3388944652
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 8 | 59594 (1)| 00:11:56 |
| 1 | SORT AGGREGATE | | 1 | 8 | | |
| 2 | TABLE ACCESS BY INDEX ROWID | SALES | 7340K| 56M| 59594 (1)| 00:11:56 |
| 3 | BITMAP CONVERSION TO ROWIDS| | | | | |
|* 4 | BITMAP INDEX SINGLE VALUE | SALES_BIX | | | | |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("CHANNEL_ID"=1)
Note
-----
- SQL plan baseline "SYS_SQL_PLAN_cd0909c333c3099a" used for this statement
The new plan, however, got recorded, but not accepted:
SQL> select SQL_TEXT,SQL_HANDLE,PLAN_NAME,ENABLED,ACCEPTED,FIXED from dba_sql_plan_baselines; SQL_TEXT SQL_HANDLE PLAN_NAME ENA ACC FIX ----------------------------- ------------------------------ ------------------------------ --- --- --- select sum(amount_sold) from SYS_SQL_ec28978ecd0909c3 SYS_SQL_PLAN_cd0909c333c3099a YES YES NO sales where channel_id=1 select sum(amount_sold) from SYS_SQL_ec28978ecd0909c3 SYS_SQL_PLAN_cd0909c335032dee YES NO NO sales where channel_id=1
An evolvement has to prove a strong improvement compared to the old
plan in order to get the new plan accepted for usage by the optimizer:
set serverout on DECLARE report clob; BEGIN report := DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE ( sql_handle => 'SYS_SQL_ec28978ecd0909c3', plan_name=>'SYS_SQL_PLAN_cd0909c335032dee' ); dbms_output.put_line(report); END; / ------------------------------------------------------------------------------- Evolve SQL Plan Baseline Report ------------------------------------------------------------------------------- Inputs: ------- SQL_HANDLE = SYS_SQL_ec28978ecd0909c3 PLAN_NAME = SYS_SQL_PLAN_cd0909c335032dee TIME_LIMIT = DBMS_SPM.AUTO_LIMIT VERIFY = YES COMMIT = YES Plan: SYS_SQL_PLAN_cd0909c335032dee ----------------------------------- Plan was verified: Time used 14.7 seconds. Failed performance criterion: Compound improvement ratio <= 1.01. Baseline Plan Test Plan Improv. Ratio ------------- --------- ------------- Execution Status: COMPLETE COMPLETE Rows Processed: 1 1 Elapsed Time(ms): 10883 3750 2.9 CPUTime(ms): 9697 2959 3.28 Buffer Gets: 49997 50091 1 Disk Reads: 46911 47217 .99 Direct Writes: 0 0 Fetches: 46911 1089 43.08 Executions: 1 1 ------------------------------------------------------------------------------- Report Summary ------------------------------------------------------------------------------- Number of SQL plan baselines verified: 1. Number of SQL plan baselines evolved: 0. PL/SQL procedure successfully completed.
Although an improvement is visible, it is not big enough to lead to acceptance of the
new plan. This SQL Plan Baseline approach is really conservative.
But we can force the acceptance:
set serverout on DECLARE report clob; BEGIN report := DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE ( sql_handle => 'SYS_SQL_ec28978ecd0909c3', plan_name=>'SYS_SQL_PLAN_cd0909c335032dee', verify=>'NO' ); dbms_output.put_line(report); END; / ------------------------------------------------------------------------------- Evolve SQL Plan Baseline Report ------------------------------------------------------------------------------- Inputs: ------- SQL_HANDLE = SYS_SQL_ec28978ecd0909c3 PLAN_NAME = SYS_SQL_PLAN_cd0909c335032dee TIME_LIMIT = DBMS_SPM.AUTO_LIMIT VERIFY = NO COMMIT = YES Plan: SYS_SQL_PLAN_cd0909c335032dee ----------------------------------- Plan was changed to an accepted plan. ------------------------------------------------------------------------------- Report Summary ------------------------------------------------------------------------------- Number of SQL plan baselines verified: 0. Number of SQL plan baselines evolved: 1. PL/SQL procedure successfully completed.
Now the optimizer is free to choose this accepted plan – or the other accepted ones.
SQL> select SQL_TEXT,SQL_HANDLE,PLAN_NAME,ENABLED,ACCEPTED,FIXED from dba_sql_plan_baselines;
SQL_TEXT SQL_HANDLE PLAN_NAME ENA ACC FIX
----------------------------- ------------------------------ ------------------------------ --- --- ---
select sum(amount_sold) from SYS_SQL_ec28978ecd0909c3 SYS_SQL_PLAN_cd0909c333c3099a YES YES NO
sales where channel_id=1
select sum(amount_sold) from SYS_SQL_ec28978ecd0909c3 SYS_SQL_PLAN_cd0909c335032dee YES YES NO
sales where channel_id=1
SQL> set autotrace on explain
SQL> select sum(amount_sold) from sales where channel_id=1;
SUM(AMOUNT_SOLD)
----------------
785647882
Execution Plan
----------------------------------------------------------
Plan hash value: 1047182207
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 8 | 13795 (2)| 00:02:46 |
| 1 | SORT AGGREGATE | | 1 | 8 | | |
|* 2 | TABLE ACCESS FULL| SALES | 7340K| 56M| 13795 (2)| 00:02:46 |
----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("CHANNEL_ID"=1)
Note
-----
- SQL plan baseline "SYS_SQL_PLAN_cd0909c335032dee" used for this statement
We can even fix one execution plan – for example for runtime comparison.
First we demand index access:
declare numplans pls_integer; begin numplans:= DBMS_SPM.ALTER_SQL_PLAN_BASELINE ( sql_handle=>'SYS_SQL_ec28978ecd0909c3', plan_name=>'SYS_SQL_PLAN_cd0909c333c3099a', attribute_name=>'FIXED', attribute_value=>'YES'); dbms_output.put_line(numplans); end; / SQL> set autotrace on explain SQL> select sum(amount_sold) from sales where channel_id=1; SUM(AMOUNT_SOLD) ---------------- 785647882 Execution Plan ---------------------------------------------------------- Plan hash value: 3388944652 ------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 8 | 59594 (1)| 00:11:56 | | 1 | SORT AGGREGATE | | 1 | 8 | | | | 2 | TABLE ACCESS BY INDEX ROWID | SALES | 7340K| 56M| 59594 (1)| 00:11:56 | | 3 | BITMAP CONVERSION TO ROWIDS| | | | | | |* 4 | BITMAP INDEX SINGLE VALUE | SALES_BIX | | | | | ------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 4 - access("CHANNEL_ID"=1) Note ----- - SQL plan baseline "SYS_SQL_PLAN_cd0909c333c3099a" used for this statement SQL> set autotrace off SQL> set timing on SQL> select sum(amount_sold) from sales where channel_id=1; SUM(AMOUNT_SOLD) ---------------- 785647882 Elapsed: 00:00:07.08
Now we demand Full Table Scan:
SQL> declare
2 numplans pls_integer;
begin
numplans:=
DBMS_SPM.ALTER_SQL_PLAN_BASELINE (
sql_handle=>'SYS_SQL_ec28978ecd0909c3',
plan_name=>'SYS_SQL_PLAN_cd0909c333c3099a',
attribute_name=>'FIXED',
attribute_value=>'NO');
dbms_output.put_line(numplans);
end;
/ 3 4 5 6 7 8 9 10 11 12
1
PL/SQL procedure successfully completed.
declare
numplans pls_integer;
begin
numplans:=
DBMS_SPM.ALTER_SQL_PLAN_BASELINE (
sql_handle=>'SYS_SQL_ec28978ecd0909c3',
plan_name=>'SYS_SQL_PLAN_cd0909c335032dee',
attribute_name=>'FIXED',
attribute_value=>'YES');
dbms_output.put_line(numplans);
end;
/
SQL> select sum(amount_sold) from sales where channel_id=1;
SUM(AMOUNT_SOLD)
----------------
785647882
Elapsed: 00:00:02.04
As you see, the new execution plan is really faster in this case – it was the right decision to allow its usage.But first, we saw a proof of it during the evolvement. So we did not trust the optimizer blindly. We had a strong control over the change process!
Addendum: Please see this excellent series of Articles regarding SQL Plan Baselines from the Optimizer Development Group:
http://optimizermagic.blogspot.com/2009/01/plan-regressions-got-you-down-sql-plan.html
http://optimizermagic.blogspot.com/2009/01/sql-plan-management-part-2-of-4-spm.html
http://optimizermagic.blogspot.com/2009/01/sql-plan-management-part-3-of-4.html
http://optimizermagic.blogspot.com/2009/02/sql-plan-management-part-4-of-4-user.html
Second Addendum: Migrate Stored Outlines to SQL Plan Baselines – Why & How
http://blogs.oracle.com/optimizer/entry/how_do_i_migrate_stored
