Karl about the Oracle Database

Some experiences out of my daily oracle practice

Archive for the 'Tuning' Category

All about Tuning

Oracle (10g) Tuning Quickstart Guide

Posted by carl.reitschuster on 2nd March 2007

Hi reader,

you find it here

HTH

Karl

Posted in UnCategorized, Tuning, 10.2 | No Comments »

The LIKE Bindvariable Dilemma

Posted by carl.reitschuster on 20th February 2007

hi,

everybody tells you to use bind variables (reduced parses) and that’s ok. But in some circumstances you get a lot of problems with it. Using bind variables with the LIKE operator could cause horrible performance even you index that column and you do not use a ‘%’ at the start of the searched string.

Case 1 comparing with = operator :

WHERE Company.Customer = :P02

                  TABLE ACCESS BY LOCAL INDEX ROWID Depth=18 Object owner=ISIS Object name=COMPANY Cost=4 CPU cost=28254 IO cost=4 Cardinality=1 Bytes=289   Options=BY LOCAL INDEX ROWID  Operation=TABLE ACCESS Optimizer=ANALYZED Partition id=18 Partition start=2 Partition stop=2
                   INDEX RANGE SCAN Depth=19 Object owner=ISIS Object name=COMPANY_IX01 Cost=3 CPU cost=21764 IO cost=3 Cardinality=1  Access predicates="COMPANY"."CUSTOMER"=:P02  Options=RANGE SCAN  Operation=INDEX Optimizer=ANALYZED Partition id=19 Partition start=2 Partition stop=2

Yeah!! the optimizer chooses the index - very fast response - in this case.

Case 2 comparing with LIKE operator :

WHERE Company.Customer LIKE :P02

                  TABLE ACCESS FULL Depth=18 Object owner=ISIS Object name=COMPANY Cost=45241 CPU cost=8354827075 IO cost=44959 Cardinality=346919 Bytes=100259591  Filter predicates="COMPANY"."CUSTOMER" LIKE :P02 Options=FULL  Operation=TABLE ACCESS Optimizer=ANALYZED Partition id=27 Partition start=2 Partition stop=2

Oh my god!!! what is Oracle doing here???? Full Tablescan over 6.6m rows. The reason for is that dummy 5% assumption. Oracle assumes to scan 5% rows (look at the expected cardinality it’s about 5% of 6.8m) of the table using LIKE with bind var. Unfortunately this is the limit for a full table scan.

We fixed that with a higher affinity to index blocks. Also there is an internal parameter known to cahnge globally the way oracle calculate the expected cardinality of the LIKE operator. But a better way in the future release of the software will be: If the queried field is full qualified (means full length without ‘%’ or ‘_’) then the comparison operator is used instead of like.

cheers

Karl

 

Posted in Tuning, 9.2, 10.2, Statistics, Execution Plan, advanced, 10.1 | No Comments »

Scalability : About

Posted by carl.reitschuster on 26th January 2007

Hi reader,

after a benchmarking test of Components of a CRM System with Oracle 10.2 and Sun Solaris with either AMD Opteron or Sun SPARC IV+ CPU’s i started to think about the definition of Scalability. Her it is:

Scalability is about how strong the increase 
of the Throughput/decrease of Response Time
of an Application or Software Component is
when additional resources of CPU, Memory and I/O are added;

A second one follows :

Scalability is about how strong the decrease
of the Throughput/increase of Response Time
of an Application or Software Component is
when the workload ( Amount of work, Amount of Concurrency ) increases;

Karl Reitschuster

Posted in Tuning, 10.2 | 2 Comments »

Pinning very large objects into the KEEP pool

Posted by carl.reitschuster on 11th December 2006

Hi reader,

Once an Oracle Block is read from the disk it gets a place in the buffer cache. How long the block remains cached depends on following parameters.

  • if it’s an index block - they are stronger bound to the DEFAULT cache then table blocks resulting in a longer period of cache life cycle.
  • if it’s a table block - it will be cached but earlier freed from cache then an index block
  • if it’s a frequently accessed (LRU mechanism) a block frequently access will remain in the buffer cache
  • if the table block is originated by a long full table scan - a block read by a scattered read on a table which does not fit in a scan window of the buffer cache - it will be displaced immediately from the next blocks.

To guarantee a longer life cycle for a table/index block another cache type i think was introduced with Oracle 8i. The KEEP pool. It tends to keep (to pin) the blocks even they are not frequently accessed and enables caching of all blocks of a segment read with scattered read and not only a subset of it. Thus it is guaranteed that access to a segment cached in KEEP pool will not have any physical I/O. Usually i learnt to keep frequently small objects for example lookup tables into the KEEP pool.

We faced a problem on our company table querying specific company types a full table scan could not prevented. If many columns are used in some reports/lists to filter the result you cannot put an index on every column of the table. The company table is about 1.7G large with about 6.5M of rows. On our test database server two concurrent queries on the company table resulted in  > 60sec instead of 25sec. And even 25 sec are too long.

Having a 64 bit system we decided to upgrade memory and to pin the Company table into the KEEP pool.

Following steps to load the company table int o the KEEP pool:

  • set SGA_TARGET to 2.8G - Database is in automatic SGA sizing mode - a restart of the database will be needed
    ALTER SYSTEM SET sga_target=2800M SCOPE=SPFILE
    ;
  • and then set DBA_KEEP_POOL_SIZE to 1.8G - This reduces available memory for other SGA (Shared pool + DEFAULT buffer cache) components to 1G 
    ALTER SYSTEM SET db_keep_cache_size=1800M SCOPE=BOTH
    ;
  • Altered the tables buffer cache
    ALTER TABLE COMPANY STORAGE (BUFFER_POOL KEEP)
    ;
  • Pin the Company Table with a FULL table scan into the keep pool. ( You must use a FULL Hint to prevent an index full scan)
    SQL> SELECT /*+FULL (COMPANY)*/ COUNT(*) FROM COMPANY;
      COUNT(*)
    ———-
       6764758
    SQL>

once loaded the table into the KEEP buffer cache the result of the query response time was < 5sec even with concurrent sessions;

Monitor Buffer Pool Usage (the physical reads on the KEEP pool were caused by the inital load (pin) of the COMPANY Table):

SQL> SELECT Vps.NAME,
  2         Vps.Consistent_Gets,
  3         Vps.Physical_Reads
  4    FROM V$BUFFER_POOL_STATISTICS Vps;

NAME                 CONSISTENT_GETS PHYSICAL_READS
——————– ————— ————–
KEEP                        35762385         194722
DEFAULT                    306683092        3568628

SQL>

To go this way successfully you need following resources

  • fast CPU; for high LIO-Rate - on our Dual Xeon’s we had a rate of 1.4G/sec
  • higher number of CPU >= 4
  • Server optimized Mainboard/Memory access
  • fast Memory

Conclusion

If your site drives a 64bit System. You could benefit from loading very large objects into the KEEP pool. Sure - this is not an all round optimisation for your weak SQL and your weak physical and logical design of your database/schema
;-)

HTH
Karl Reitschuster

 

Posted in Tuning, 9.2, 10.2, advanced, 10.1 | No Comments »

Experiencing ASH and extended trace 10046 event

Posted by carl.reitschuster on 31st October 2006

Hi Reader,

Inserting on a table with a lot of indexes could cause the wait event db file sequential read. For every row to be inserted the index must be scanned and updated when an indexed column was specified in the DML statement. With about 150 rows a second the insert operation was very slow. I enabled extended trace 10046 event and reviewed the generated raw trace file;

The traditionaly way - trace files

In the 10046 event trace file you will find the Cursor definiton section of the insert statement:


=====================

PARSING IN CURSOR #8 len=693 dep=1 uid=50 oct=2 lid=50 tim=226692485 hv=1579689243 ad=’396bb604

INSERT INTO COMPANY () VALUES (:B18 , NULL, 0, NULL, NULL, SYS_GUID(), :B17 , NULL, NULL, :B16 , NULL, NULL, NULL, :B15 , NULL, NULL, :B14 , NULL, NULL, NULL, :B13 , :B12 , NULL, :B11 , :B10 , :B9 , :B8 , NULL, NULL, :B7 , :B6 , :B5 , :B4 , :B3 , :B2 , :B1 )

END OF STMT

Corresponding to the Cursor number #8 wait events are collected line by line;

WAIT #8: nam=’db file sequential read’ ela= 23606 file#=11 block#=169935 blocks=1 obj#=73317 tim=251852023
WAIT #8: nam=’db file sequential read’ ela= 21557 file#=15 block#=201225 blocks=1 obj#=84340 tim=251873687
WAIT #8: nam=’db file sequential read’ ela= 9502 file#=5 block#=112658 blocks=1 obj#=73317 tim=251885511
WAIT #8: nam=’db file sequential read’ ela= 8457 file#=5 block#=119421 blocks=1 obj#=73317 tim=251906307
WAIT #8: nam=’db file sequential read’ ela= 9933 file#=11 block#=181310 blocks=1 obj#=73320 tim=251920477

The insert Statement is waiting on  ‘db file sequential read’; This means index blocks need to be updated for every inserted row. An update operation always causes a read operation. Then you see the elapsed time waited on the event and the corresponding Oracle Block :

ela= 23606 file#=11 block#=169935 blocks=1

The last columns of the wait event line show the object on which the wait occurred. In this case it identifies the index object. It’s the object id defined/visible in the DBA_OBJECTS view;

obj#=73317

Now it would be interesting to know on which index segments the highest amount of Waits is. First i thought to parse the trace file and aggregate the wait event timings dependent on the objects id of the index. But this is a lot of work!

The work is done - already

And that’s the point i want to address : this work is already done with Oracle 10.2. The new Active Session History holds a wait history for all events in a static V$ table. So you need not to parse a raw 10046 trace file but you can use simply SQL to create your stats summary.

Before we take a look on the Active Session History it would be helpful to identify the SQL Statement. For this Oracle 10 introduces the SQL_ID; this id presented as raw value is unique for a SQL statement and stored to identify the SQL in the AWR Repository. So let’s query the most I/O related SQL :

SELECT *
 
FROM (SELECT Sql_Text,
               Sql_Id
,
               Elapsed_Time
,
               Cpu_Time
,
               User_Io_Wait_Time
         
FROM Sys.v_$sqlarea
        
ORDER BY 5 DESC)

 
WHERE Rownum <= 10

;

SQL>

SQL> SELECT *

  2    FROM (SELECT Sql_Text,

  3                 Sql_Id,

  4                 Elapsed_Time,

  5                 Cpu_Time,

  6                 User_Io_Wait_Time

  7            FROM Sys.v_$sqlarea

  8           ORDER BY 5 DESC)

  9   WHERE Rownum <= 10

 10 ;

 

SQL_TEXT                                                                         SQL_ID        ELAPSED_TIME   CPU_TIME USER_IO_WAIT_TIME

——————————————————————————– ————- ———— ———- —————–

DECLARE job BINARY_INTEGER := :job; next_date DATE := :mydate;  broken BOOLEAN : ghd7756swyr57   8086056419 2354027440        5646498847

insert into wri$_adv_objspace_trend_data select timepoint,  space_usage, space_a 8szmwam7fysa3   5349309477   34476481        5328638621

call ISIS.JOB_SAMPLE_STATS ( :0 )                                                2zqxbb6vcczc5   6272593702 2342870666        3860113334

DECLARE job BINARY_INTEGER := :job; next_date DATE := :mydate;  broken BOOLEAN : 6gvch1xu9ca3g   2745079215  290436248        2092929799

CALL MGMT_ADMIN_DATA.EVALUATE_MGMT_METRICS(:tguid, :mguid, :result)              abtp0uqvdb1d3   2014214236   50339448        1920152264

INSERT INTO COMPANY (COKISSTREET, LEGAL, ISIS_PREFERED, INDUSTRYSUBTYPEKEY, MAIN 9kn5h8xg2h98v   1457505383  163517071        1327894552

BEGIN   – Call the procedure   Eventlogmgr.Begintask;   Util_Session.Work_Area_ 9z93xgg5anujn   1437976040  200453636        1238418231

select /*+ no_parallel(t) no_parallel_index(t) dbms_stats cursor_sharing_exact u 82bks68vck6b8   2517148726 1375255579        1053152419

select /*+ no_parallel_index(t,"COMPANY_FX01") dbms_stats cursor_sharing_exact u f5g9fzrg3zcky   1005457671   18158338         985640727

select /*+ no_parallel(t) no_parallel_index(t) dbms_stats cursor_sharing_exact u 4jjhy9autcxu6   1133956193  186810975         933561254

 

10 rows selected

 

SQL>

With the SQL_ID it is possible to query against the Active Session History. Following SQL summarized all waiting times of an SQL dependent on the object on which the wait event occurred. 

SELECT Vash.Event,
      
SUM(Vash.Time_Waited) AS Total_Time_Waited,
       Do.Object_Id
,
       Do.Owner
,
       Do.Object_Name
 
FROM Sys.v_$active_Session_History Vash,
      
Sys.Dba_Objects               Do
 
WHERE Vash.Sql_Id = ‘9kn5h8xg2h98v’

  
AND Vash.Current_Obj# = Do.Object_Id
  
AND Time_Waited <> 0

 
GROUP BY Vash.Event,
          Do.Object_Id
,
          Do.Owner
,
          Do.Object_Name

;


SQL>

SQL> SELECT Vash.Event,

  2         SUM(Vash.Time_Waited) AS Total_Time_Waited,

  3         Do.Object_Id,

  4         Do.Owner,

  5         Do.Object_Name

  6    FROM Sys.v_$active_Session_History Vash,

  7         Sys.Dba_Objects               Do

  8   WHERE Vash.Sql_Id = ‘9kn5h8xg2h98v’

  9     AND Vash.Current_Obj# = Do.Object_Id

 10     AND Time_Waited <> 0

 11   GROUP BY Vash.Event,

 12            Do.Object_Id,

 13            Do.Owner,

 14            Do.Object_Name

 15 ;

 

EVENT                                                            TOTAL_TIME_WAITED  OBJECT_ID OWNER                          OBJECT_NAME

—————————————————————- —————– ———- —————————— ——————————————————————————–

db file sequential read                                                  135909921      73317 ISIS                           COMPANY_FX02

db file sequential read                                                    5174869      72587 ISIS                           COMPANY

db file sequential read                                                   79492328      73316 ISIS                           COMPANY_FX01

db file sequential read                                                   38163394      84340 ISIS                           COMPANY_UK02

db file sequential read                                                      83892      73315

ISIS                           COMPANY_IX01

db file sequential read                                                   28944084      73320 ISIS                           COMPANY_FX05

log file switch completion                                                  295991      84340 ISIS                           COMPANY_UK02

db file sequential read                                                   45375422      72588 ISIS                           COMPANY_PK

 

8 rows selected

 

SQL>

You clearly can see on which indexes the insert statement opration has to wait and which indexes causes the largest amount of waits. In this case the COMPANY_FX02 index consumes about 30% of all waits.

Why still in need of the old 10046 traces

  • The detailed Active Session History exists only in memory
    Few minutes later after the events are recorded dependent on database load and SGA size the sampled wait events details could be gone. Before deletion they are stored in the AWR in aggregated format.
  • You see not the whole picture
    Tracing the INSERT Statement gives you stats about all secondary activities the INSERT statement causes due to triggers or materialized views and replication. These SQL are the so called recursive SQL statements. In the current example the table COMPANY had a Materialized View Log defined which caused a lot of elapsed time too (a section in the tkprof prepared file of the raw 10046 trace):

SELECT DISTINCT LOG$."ID"
FROM
 (SELECT MLOG$."ID" FROM "ISIS"."MLOG$_COMPANY1" MLOG$ WHERE "SNAPTIME$$" >
  :1 AND ("DMLTYPE$$" != ‘I’)) LOG$ WHERE (LOG$."ID") NOT IN (SELECT
  MAS_TAB$."ID" FROM "ISIS"."COMPANY" "MAS_TAB$" WHERE
  ("MAS_TAB$"."ISIS_PREFERED"=1) AND LOG$."ID" = MAS_TAB$."ID")

call     count       cpu    elapsed       disk      query    current        rows
——- ——  ——– ———- ———- ———- ———-  ———-
Parse       31      0.00       0.00          0          0          0           0
Execute     31      0.03       0.00          0          0          0           0
Fetch       31      0.85      55.76       4077      18835          0        4417
——- ——  ——– ———- ———- ———- ———-  ———-
total       93      0.89      55.77       4077      18835          0        4417

Misses in library cache during parse: 1
Misses in library cache during execute: 1
Optimizer mode: ALL_ROWS
Parsing user id: 50     (recursive depth: 2)

Rows     Row Source Operation
——-  —————————————————
    200  HASH UNIQUE (cr=837 pr=198 pw=0 time=2888263 us)
    200   FILTER  (cr=837 pr=198 pw=0 time=2879283 us)
    200    TABLE ACCESS FULL MLOG$_COMPANY1 (cr=37 pr=0 pw=0 time=743 us)
      0    TABLE ACCESS BY INDEX ROWID COMPANY (cr=800 pr=198 pw=0 time=2877308 us)
    200     INDEX UNIQUE SCAN COMPANY_PK (cr=600 pr=198 pw=0 time=2875219 us)(object id 72588)

Conclusion

With the Active Session History Oracle gives you an instrument for ad hoc tuning. An easy interface querying Statement statistics via SQL. For the whole picture and further statement dependencies the 10046 trace is still the choice.

HTH
Karl

Posted in UnCategorized, 10.2, 10046 trace | No Comments »

Event : extraordinary concentration of CBO knowhow in Europe, Zürich 21./22. of June

Posted by carl.reitschuster on 4th May 2006

Hi reader,

Trivadis a Database oriented solution provider invited a number of oracle experts for two days in Zürich to explore the CBO (Cost Based Optimizer). A very interesting event as you would meet there 

Really an outstanding event, more details on Trivadis 2-Days-Seminar on Oracle CBO

Greetings

Karl

Posted in CBO, Event | No Comments »

Slow Oracle BLOB upload with C# .NET and how to speed up.

Posted by carl.reitschuster on 2nd May 2006

Hi reader,

Gerhard is a colleague in the current project i work. An upload of documents via .NET was very slow (1M in 40 seconds). We found a workaround and reached 1M in 2 seconds. Here the article published in Gerhard’s Blog;

Slow Oracle BLOB upload with C# .NET and how to speed up.

Greetings
Karl

Posted in Tuning, 9.2, 10.2, advanced | No Comments »

No more Hints in Oracle 10G?

Posted by carl.reitschuster on 27th April 2006

Hi reader,

With Oracle 10G specially with 10.2 you often hear :

! DO NOT USE HINTS ANY MORE! THEY ARE NOT NEEDED ANY MORE ! 

So thinking of the possibility to use a hint for a statement gives you a bad feeling doing something wrong. It’s clear to use hints only if there is no way to bring the optimizer to the desired execution plan or if the plan stability is most important even independent from gathered table/index stats.

Yesterday i navigated with the Db Console thru some SQL-Statements run in the past. Accidentally i found an interesting statement:

As you see this SELECT statement uses the NO_MERGE and ORDERED hint. It was executed via Db Console well known as an Oracle product ;-). And even the RULE hint seems (it’s not) to be out in Oracle 10.2, with these hints you dictate the query optimizer what plan to generate.

The SQL again - better formatted for discussion:

SELECT Task_List.Task_Id
 
FROM (SELECT /*+ NO_MERGE(T) ORDERED */

         t.Task_Id
         
FROM (SELECT *
                 
FROM Dba_Advisor_Tasks
                
ORDER BY Task_Id DESC) t,

               Dba_Advisor_Parameters_Proj P1
,
               Dba_Advisor_Parameters_Proj P2
        
WHERE t.Advisor_Name = ‘ADDM’
          
AND t.Status = ‘COMPLETED’
          
AND t.Execution_Start >= (SYSDATE - 1)
          
AND t.How_Created = ‘AUTO’
          
AND t.Task_Id = P1.Task_Id
           
AND P1.Parameter_Name = ‘INSTANCE’

          
AND P1.Parameter_Value = Sys_Context(‘USERENV’,
                                               
‘INSTANCE’)
          
AND t.Task_Id = P2.Task_Id
          
AND P2.Parameter_Name = ‘DB_ID’

          
AND P2.Parameter_Value = To_Char(:B1)
        
ORDER BY t.Task_Id DESC) Task_List
 
WHERE Rownum = 1

The NO_MERGE hint dictates the optimizer not to merge the sub query


SELECT *
 
FROM Dba_Advisor_Tasks
 
ORDER BY Task_Id DESC

into the rest of the statement, some kind of rewriting the query for performance reasons. This means the sub query is executed as it is written.

The ORDERED hint dictates the optimizer the join order of the tables.  The join orders of the tables then is depending on the order in the FROM table list . So the execution plan accesses T(Dba_Advisor_Tasks), P1(Dba_Advisor_Parameters_Proj), P2(Dba_Advisor_Parameters_Proj);

Let’s take a look at the EXPLAIN PLAN :

SQL> explain plan for
  2  SELECT Task_List.Task_Id
  3    FROM (SELECT /*+ NO_MERGE(T) ORDERED */
  4           t.Task_Id
  5            FROM (SELECT *
  6                    FROM Dba_Advisor_Tasks
  7                   ORDER BY Task_Id DESC) t,
  8                 Dba_Advisor_Parameters_Proj P1,
  9                 Dba_Advisor_Parameters_Proj P2
 10           WHERE t.Advisor_Name = ‘ADDM’
 11             AND t.Status = ‘COMPLETED’
 12             AND t.Execution_Start >= (SYSDATE - 1)
 13             AND t.How_Created = ‘AUTO’
 14             AND t.Task_Id = P1.Task_Id
 15             AND P1.Parameter_Name = ‘INSTANCE’
 16             AND P1.Parameter_Value = Sys_Context(’USERENV’,
 17                                                  ‘INSTANCE’)
 18             AND t.Task_Id = P2.Task_Id
 19             AND P2.Parameter_Name = ‘DB_ID’
 20             AND P2.Parameter_Value = To_Char(:B1)
 21           ORDER BY t.Task_Id DESC) Task_List
 22   WHERE Rownum = 1
 23  ;

EXPLAIN PLAN ausgef³hrt.

SQL> SELECT * FROM table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
————————————————————————————————————
Plan hash value: 2107564592

———————————————————————————————————–
| Id  | Operation                        | Name                   | Rows  | Bytes | Cost (%CPU)| Time     |
———————————————————————————————————–
|   0 | SELECT STATEMENT                 |                        |     1 |    13 |    19   (6)| 00:00:01 |
|*  1 |  COUNT STOPKEY                   |                        |       |       |            |       |
|   2 |   VIEW                           |                        |     1 |    13 |    19   (6)| 00:00:01 |
|*  3 |    SORT ORDER BY STOPKEY         |                        |     1 |   102 |    19   (6)| 00:00:01 |
|   4 |     NESTED LOOPS                 |                        |     1 |   102 |    18   (0)| 00:00:01 |
|   5 |      NESTED LOOPS                |                        |     1 |    78 |    17   (0)| 00:00:01 |

PLAN_TABLE_OUTPUT
————————————————————————————————————
|   6 |       VIEW                       |                        |     1 |    54 |    16   (0)| 00:00:01 |
|*  7 |        TABLE ACCESS FULL         | WRI$_ADV_TASKS         |     1 |    29 |    16   (0)| 00:00:01 |
|*  8 |       TABLE ACCESS BY INDEX ROWID| WRI$_ADV_PARAMETERS    |     1 |    24 |     1   (0)| 00:00:01 |
|*  9 |        INDEX UNIQUE SCAN         | WRI$_ADV_PARAMETERS_PK |     1 |       |     1   (0)| 00:00:01 |
|* 10 |      TABLE ACCESS BY INDEX ROWID | WRI$_ADV_PARAMETERS    |     1 |    24 |     1   (0)| 00:00:01 |
|* 11 |       INDEX UNIQUE SCAN          | WRI$_ADV_PARAMETERS_PK |     1 |       |     1   (0)| 00:00:01 |
———————————————————————————————————–

The Explain Plan confirms the effectiveness of the described hints. In the example you see another interesting point. The plan starts with TABLE ACCESS FULL OPERATION on WRI$_ADV_TASKS. As next operation after a full table scan i would expect a HASH JOIN Operation. But the Optimizer uses a NESTED LOOPS instead. Why?

It’s all about expected CARDINALITY[1]! Because only 1 Row is expected via TABLE ACCESS FULL Operation a NESTED LOOPS Join Operation is the correct answer.

I removed the Predicate[2] Information section for better overview. How would be the Explain Plan without Hints?

———————————————————————————————————–
| Id  | Operation                        | Name                   | Rows  | Bytes | Cost (%CPU)| Time     |
—————R