Performance Tuning for Oracle
by Andrew Fraser
- Code Listings
- More 10046 tracing
- Memory on 32 Bit Windows
- Introduction to PL/SQL
- Presentation by Allan Young, in pdf or pptx format.
- Tkprof
- SQL Tuning
- Cary Milsap, Diagnosing Performance Problems
- 10g Performance Tuning Guide
- 9iR2 Performance Tuning Guide
- 9iR1 Performance Tuning Guide
- 8i Performance Tuning Guide
- 7 Performance Tuning Guide
Coding Best Practice
- Comments - always author, date, purpose, change history at top
- Style - Code blocks, as little code as possible and readable, write for debugging
- SQL - create view, ||, SQL from SQL, inline views
- PL/SQL - loops
- Shell - vi, sed, awk, grep, functions
- SQL*Plus - set define off verify off pages 0, col, (ttitle btitle)
Performance Tuning - Methodology
- Old method - 'guess and grimace' with ratios
- New method - diagnose with oracle wait interface
Performance Tuning - Tuning Steps
- Quickly check the obvious database/server wide settings with:
- Check database and listener are up and that you can login ok
- Check alert log
- Check system OS log (/var/adm/messages, windows event viewer, dmesg, ?aix)
- Run sar. If cpu is high, find out why with top or topas or "/usr/ucb/ps aux|head". Can use spid.sql to identify oracle session behind unix process)
- sho sga - check buffer cache is not too low.
- cat init.ora/spfile - check no crazy settings
- are tables analyzed? - check last_analyzed column.
- run sw.sql to check what sessions are waiting on. If it is locks/enqueues, run ?/rdbms/admin/utllockt to identify cause.
- Look for any huge high read SQL that is obviously the problem
- Use 10046 trace to identify problem precisely, looking up any unusual waits in the oracle wait interface book.
- SQL Tuning - Check for quick win easy changes (remove hints, sort_area_size, hash_join_enabled, check tables analyzed)
- SQL Tuning - Check for missing indexes
- SQL Tuning - Start rewriting code
utllockt.sql
Run ?/rdbms/admin/utllock.sql to identify who holds locks. Lock wait events are named "enqueue" (for normal row data locks) or "latch free" (internal short duration lock).
SQL Tuning Steps
Book for this is Mark Gurry, Oracle SQL Tuning Pocket Reference, O'Reilly 2002.
- Tables analyzed, and stats up to date
- Try stripping out any hints.
- Try easy parameter changes:
alter session set workarea_size_policy=manual;
alter session set sort_area_size = 1048576000 ;
alter session set sort_area_size = 16384 ;
alter session set optimizer_mode=first_rows;
alter session set cursor_sharing=exact ;
alter session set optimizer_index_cost_adj = 10 ;
- Identify what columns could have indexes used. See which are currently indexed.
- Histograms
- Code rewrites:
Switch 'in' and 'exists', 'not in' and 'not exists', add 'is not null' to 'not in' for hash anti joins.
Remove outer joins
SQL processing rather than plsql loops - with upsert/merge perhaps.
- Data warehouses/reporting: bitmap indexes, materialised views.
create materialised view mv_workorder as ...
CREATE MATERIALIZED VIEW LOG ON workorder
WITH ROWID, SEQUENCE (time_id, calendar_year)
INCLUDING NEW VALUES;
CREATE MATERIALIZED VIEW LOG ON times
WITH ROWID, SEQUENCE (prod_id)
INCLUDING NEW VALUES;
CREATE MATERIALIZED VIEW sales_mv
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
AS SELECT t.calendar_year, p.prod_id,
SUM(s.amount_sold) AS sum_sales
FROM times t, products p, sales s
WHERE t.time_id = s.time_id AND p.prod_id = s.prod_id
GROUP BY t.calendar_year, p.prod_id;
Common SQL issues
- Missing indexes
- Function around indexed column - remove function or create a function based index
- Unnecessary subqueries - could be replaced with a simple join instead (less of an issue with newer versions)
- Unnecessary outer joins - test performance effect of removing the (+)'s
- Better to replace IN with EXISTS or replace EXISTS with IN
- Hash anti join not used with NOT exists / NOT in
- OR with subqueries being used instead of UNION of different queries -
see this on Maximo
- Table or index has had many deletes, very few rows in each block - rebuild
- Inappropriate view being used - has joins that aren't needed by this particular statement
- IF-ELSE or multiple SQL statements being used instead of one CASE/DECODE statement
- Bind variables not being used - although is only an issue if a lot of time/cpu is being spent parsing.
- Skewed data - use histograms with analyze/dbms_stats
- Old stats on tables - reanalyze
- Missing stats on tables - analyze
- Incorrect init.ora settings - test effect of removing them through alter session
- (strictly a functional error rather than a performance issue) OR's can be dangerous - if brackets
are missed from around them, results will be wrong, and also performance will be poor.
Why is my index not being used?
- where surname != 'FRASER'
- where upper(surname) = 'FRASER'
- where surname is null
- where surname like '%FRASER'
- small tables
- histograms:
90% rows 'Yes'
1% rows 'No'
1% 'Maybe'
1% 'Not in'
1% 'Later'
where answer = 'Yes'
to 8i where answer = ':b1'
9i and up bind variable peeking:
- first time - where answer = ':b1' - :b1='Yes' - then no index FOREVER
- first time - where answer = ':b1' - :b1='Maybe' - then index ALWAYS
Note that is possible to permanently cache small tables:
alter system set db_keep_cache_size=200m scope=both ;
alter table small_table storage (buffer_pool keep);
Also note that it is possible to create function based indexes:
create index myindex on customer(upper(surname)) ;
In order to fix:
select * from customer where upper(surname) = 'FRASER' ;
Tom Kyte has an article on function based indexes.
Which is faster - EXISTS or IN?
Depends on the relative sizes:
select from outer_query where x IN ( select y from subquery )
select from outer_querry where EXISTS ( select null from subquery where x=y )
IN is faster than EXISTS for subqueries that return few rows.
EXISTS is faster than IN for subqueries that return many rows.
Fastest is |
Small subquery |
Huge subquery |
Small outer query |
IN |
EXISTS |
Huge outer query |
IN |
Either, varies |
EXISTS can benefit from indexes on the joining column.
Which is faster - NOT EXISTS or NOT IN?
Note that unlike EXISTS and IN, these two aren't logically the same, produce different resultsets. NULLs in the subquery completely throw NOT IN. For example:
SQL> create table t1 (c1 number) ;
Table created.
SQL> create table t2 (c2 number) ;
Table created.
SQL> insert into t1 values ( 1 ) ;
1 row created.
SQL> insert into t1 values ( 2 ) ;
1 row created.
SQL> insert into t2 values ( 1 ) ;
1 row created.
SQL> select * from t1 where c1 not in ( select c2 from t2 ) ;
C1
----------
2
SQL> select * from t1 where not exists ( select null from t2 where c1 = c2 ) ;
C1
----------
2
SQL> insert into t2 values ( null ) ;
1 row created.
SQL> select * from t1 where c1 not in ( select c2 from t2 ) ;
no rows selected
SQL> select * from t1 where not exists ( select null from t2 where c1 = c2 ) ;
C1
----------
2
SQL> select * from t1 where c1 not in ( select c2 from t2 where c2 is not null ) ;
C1
----------
2
Hash Anti Joins are very much faster for NOT IN / NOT EXISTS.
- With 8i and earlier - hash anti joins are not enabled by default, you must set the always_anti_join
parameter to 'hash' first, either at session or database level. Also, the subqueries must exclude nulls.
- With 9i and 10g - hash anti joins are allowed by default, although the subqueries must still exclude nulls.
- With 11g - "null aware hash anti joins" work even with nulls.
NOT EXISTS can benefit from indexes on the joining column.
Upsert / Merge
Merge is a SQL command that performs a series of conditional update and/or insert operations. A row is updated if it exists, inserted if it doesn't. Merge was first introduced with Oracle 9i.
Example:
MERGE INTO emp e1 USING emp_load e2 ON (e2.empno = e1.empno)
WHEN MATCHED THEN
update set e1.sal = e2.sal
WHEN NOT MATCHED THEN
insert (empno, ename, job, mgr, hiredate, sal, comm, deptno)
values (e2.empno, e2.ename, e2.job, e2.mgr, e2.hiredate, e2.sal, e2.comm, e2.deptno);
This is much faster than the old plsql equivalent of checking each row individually, then deciding whether to update it or insert it, one at a time.
You even find developers coding straight inserts and updates individually inside plsql loops. For example:
Fast:
update emp set emp.sal = ( select emp_load.sal from emp_load where emp.empno=emp_load.empno ) ;
Slow:
for data in ( select sal from emp_load)
loop
update emp set emp.sal=data.sal where emp.empno=data.empno ;
end loop;
/
Fast:
insert into emp (empno, sal) select empno, sal from emp_load ;
Slow:
for data in ( select empno,sal from emp_load)
loop
insert into emp (empno, sal) values data.empno, data.sal ;
commit ;
end loop;
/
The placing of the commit in the last example makes it even slower.
In general, aim to code in single operation SQL statements, rather than repeating lots of individual statement executions.
Use CASE (formerly DECODE) for performance
This does the work of five queries in one go:
SELECT last_name, job_id, salary,
(CASE
WHEN job_id LIKE 'SA_MAN' AND salary < 12000 THEN '10%'
WHEN job_id LIKE 'SA_MAN' AND salary >= 12000 THEN '15%'
WHEN job_id LIKE 'IT_PROG' AND salary < 9000 THEN '8%'
WHEN job_id LIKE 'IT_PROG' AND salary >= 9000 THEN '12%'
ELSE 'NOT APPLICABLE'
END ) Raise
FROM employees;
Case can also be used instead of plsql if-then-else logic.
Before 9i, decode was used for this functionality instead. It does the same as case, but is not as easy to read or debug.
Performance Tuning - Miscellaneous
- Hit ratios
- sga_target
- ORA-4031 unable to allocate %s bytes of shared memory
- Checkpoints
- Shared pool latches
- Cursor sharing / bind variables. Prepared statement forces a hard pass, so prepare once, execute over and over swapping bind variables each time.
- Wait events
- Overview statspack/awr
- Sorts - direct path reads/writes, used for hash also.
- Explain plan / autotrace
- 10046 trace
- tkprof
- Catting trace files pre tkprof
- Optimizer stats
- Block size - dbfmbrc 1m
- Migration / chaining
- Partitioning - generally no use for performance. But make sure sas big enough to hold them.
- Btree indexes
- bitmap indexes
- reverse key indexes
- IOTs
- function based indexes.
- Full text indexes.
- Views (or big complex SQL) instead of complex application logic.
- Materialised views
- SQL tuning - how to find missing indexes
- Why is my index not being used?
- Identify slow SQL from v$sql
- Identify what SQL is running from v$session-v$sql
- Identify wait events from v$session_wait
- PLSQL inefficiencies
- Context switches
- sysdate calls
- bulk collects
- plsql functions on a column (hub) - cant collect accurate histograms
- Lobs/Longs
- Nested subqueries
- Tuning - database settings
- Now not often found as issues. But still have to check.
- Low buffer cache - hit ratio
- Low shared pool
- Low sort area size / pga_aggregate_target - if many disk sorts or I/O on temp tablespaces
- Users should use a temporary tablespace
- Rollback segments - waits, extends, v$rollstat
- Log_buffer - smaller is better
- Locks enqueues latches - catblock.sql
- Plsql - dbms_application_info - to update module/action in v$session, v$session_longops.
- Plsql - go easy on the commits.
- Plsql - Use sql blocks where possible
- Bind variables - cursor_sharing=similar.
- DW Temporary tables
- DW Materialised views
- DW Processed_flag columns
- DW Bitmap indexes
- Insert append
- Upsert / merge
- Parallel Query
- shared server / multi threaded server
- bulk collect
- upgrade hardware
- lobs/longs
- Buffer pool keep
- compress tables and indexes
- on line redo logs - should switch at most every 20-30 mins or so, even during peak times. Check with v$archived_log.
- missed 1: sequence caches
- missed 2: veritas quick i/o
- missed 3: trace analyzer
The SGA
- Around 3/4 of the physical memory should be assigned in total to all of oracle
- around half of that to SGA, and half to PGA
- Before 9i, the SGA had to be sized individually. There were three obligatory components:
- Buffer cache - big, most of the sga.
- Shared pool - medium.
- Log buffer - tiny, less than 1m, preferrably far less than 1m.
- And two optional components:
- large pool - small, used only for rman and parallel query
- java pool - small, used only for java programs (if any).
- For data warehouses, the memory split should be around 1/3 to SGA and 2/3 to PGA
- For windows servers with virus scanners, only around 2/3 of the physical memory should be assigned to oracle
Performance Tuning - Operating System
-
Steve Adams on Direct I/O Long story short: set filesystemio_options to 'setall'. Applies from 9iR2 only (was hidden parameter before).
- 10gR2 Solaris kernel parameters
- 9iR2 Linux kernel parameters
- 9iR2 Solaris kernel parameters
- nasty Oracle bug with 10.2.0.3 on Solaris
that prevents use of direct io, and degrades performance if filesystemio_options=setall
- IBM AIX Configuration and Tuning for Oracle (pdf)
- IBM Tuning IBM AIX 5L for an Oracle Database (pdf)
- Linux Werner Puschitz
- Linux Summary
- Fs v db buffer cache - veritas quick i/o / veritas storage foundation for oracle
- 1m dbfmbrc - but watch optimizer
- Server physical memory and cpu's. (/usr/sbin/prtdiag, top, topas, 'sho cpu', mpstat -l)
- AIX Virtual Memory Manager
- Direct I/O
- Linux HugePages
- Identify version of unix: uname -a
- Solaris memory and cpu: /usr/sbin/prtdiag.
On old versions, prtdiag could be in a different location, find with "find /usr -name prtdiag 2>/dev/null".
In zone virtual servers you can get the error "prtdiag can only be run in the global zone" - in that case, top will show the memory and mpstat (or sho cpu) the number of cpus, but I don't know how to find the cpu speed in that case.
- Solaris messages: /var/adm/messages
- Solaris filesystems: /etc/vfstab
- Solaris swap: swap -s
- Solaris top: top or prstat or "/usr/ucb/ps aux|head"
- Solaris shared memory: ipcs -bm
- AIX memory and cpu: : prtconf
- AIX memory: prtconf or topas
- AIX cpus: prtconf, mpstat (or sho cpu) shows the number of cpus.
- AIX messages: errpt -a|more
- AIX filesystems: /etc/filesystems
- AIX swap: lsps -a
- AIX top: topas or "ps aux|head"
- AIX shared memory: ipcs -bm
- Linux memory: "cat /proc/meminfo" or free or top. Free displays memory in kb.
- Linux CPUs: cat /proc/cpuinfo
- Linux messages: dmesg
- Linux filesystems: /etc/fstab
- Linux swap: free
- Linux memory details: cat /proc/meminfo
- Linux top: top or "ps aux --sort -%cpu | head -20"
- Linux shared memory: ipcs -m
- All servers: SAN message log is /var/log/sddsrv.log or /var/adm/sddsrv.log
- All servers: "mount" shows the currently in play filesystems mount options
Suggested Timetable
  Day 1 Morning   |
  Introduction and overview |
  Presentation in pdf or pptx format |
  Day 1 Afternoon   |
  10046 trace |
  Day 2 Morning   |
  Tkprof |
  Top SQL from statspack or v$sql   |
  Day 2 Afternoon   |   SQL tuning |
  Day 3 Morning   |   Initial checks |
  The SGA and the PGA |
  Miscellaneous tuning |
  Day 3 Afternoon   |   Operating System performance |