SQL Statement History Analysis (sphistory.sql)
What This Script Does
Section titled “What This Script Does”This sophisticated script provides comprehensive SQL statement performance history analysis by:
- Tracking historical performance trends for a specific SQL statement using Statspack data
- Displaying execution plan changes over time with detailed plan information
- Calculating incremental performance metrics between snapshots
- Generating detailed reports showing performance degradation or improvement patterns
- Supporting multi-instance RAC environments with instance-specific analysis
Script
Section titled “Script” *********************************************************************/set echo offset feedback off timing off verify off linesize 100 pagesize 130 recsep offset serveroutput on size 1000000 format wrapped trimout on trimspool oncol snap_time format a12 truncate heading "Snapshot|Time"col execs format 999,990 heading "Execs"col lio_per_exec format 999,999,999,990.00 heading "Avg LIO|Per Exec"col pio_per_exec format 999,999,999,990.00 heading "Avg PIO|Per Exec"col cpu_per_exec format 999,999,999,990.00 heading "Avg|CPU (secs)|Per Exec"col ela_per_exec format 999,999,999,990.00 heading "Avg|Elapsed (secs)|Per Exec"col sql_text format a64 heading "Text of SQL statement"clear breaks computesttitle offbtitle off
accept V_HASH_VALUE prompt "Enter the SQL statement hash value: "accept V_ORACLE_SID prompt "Enter the SID of the Oracle instance (wildcard chars permitted): "
spool sphistory_&&V_HASH_VALUE
select sql_textfrom stats$sqltextwhere hash_value = &&V_HASH_VALUEorder by text_subset, piece;
set linesize 0 heading offselect decode(count(*),0,'CSTATS$PLAN_TABLE DOES NOT EXIST -- PLEASE RUN SCRIPT "top_stmt4_9i.sql" TO CREATE IT', '') messagefrom user_tableswhere table_name = 'CSTATS$PLAN_TABLE';set linesize 130 heading on
declare cursor get_plan_hash_value(in_hash_value in number) is select pu.plan_hash_value, ss.snap_time, ss.snap_id from stats$sql_plan_usage pu, stats$snapshot ss where pu.dbid = ss.dbid and pu.instance_number = ss.instance_number and pu.snap_id = ss.snap_id and pu.hash_value = in_hash_value order by ss.snap_time; -- cursor get_xplan(in_plan_hv in number) is select plan_table_output from table(dbms_xplan.display('CSTATS$PLAN_TABLE', trim(to_char(in_plan_hv)), 'ALL')); -- v_prev_plan_hash_value number := -1; v_text_lines number := 0; v_errcontext varchar2(100); v_errmsg varchar2(100);begin -- v_errcontext := 'open/fetch get_plan_hash_value'; for phv in get_plan_hash_value(&&V_HASH_VALUE) loop -- if v_prev_plan_hash_value <> phv.plan_hash_value then -- v_prev_plan_hash_value := phv.plan_hash_value; -- v_errcontext := 'insert into cstats$plan_table'; insert into cstats$plan_table ( STATEMENT_ID, TIMESTAMP, REMARKS, OPERATION, OPTIONS, OBJECT_NODE, OBJECT_OWNER, OBJECT_NAME, OBJECT_INSTANCE, OBJECT_TYPE, OPTIMIZER, SEARCH_COLUMNS, ID, PARENT_ID, POSITION, COST, CARDINALITY, BYTES, OTHER_TAG, PARTITION_START, PARTITION_STOP, PARTITION_ID, OTHER, DISTRIBUTION, CPU_COST, IO_COST, TEMP_SPACE, ACCESS_PREDICATES, FILTER_PREDICATES) select trim(to_char(p.PLAN_HASH_VALUE)), SYSDATE, 'hash_value = '''||p.PLAN_HASH_VALUE||''' from STATS$SQL_PLAN', p.OPERATION, p.OPTIONS, p.OBJECT_NODE, p.OBJECT_OWNER, p.OBJECT_NAME, p.OBJECT#, o.OBJECT_TYPE, p.OPTIMIZER, p.SEARCH_COLUMNS, p.ID, p.PARENT_ID, p.POSITION, p.COST, p.CARDINALITY, p.BYTES, p.OTHER_TAG, p.PARTITION_START, p.PARTITION_STOP, p.PARTITION_ID, p.OTHER, p.DISTRIBUTION, p.CPU_COST, p.IO_COST, p.TEMP_SPACE, p.ACCESS_PREDICATES, p.FILTER_PREDICATES from stats$sql_plan p, stats$seg_stat_obj o where p.plan_hash_value = phv.plan_hash_value and o.obj# (+) = p.object#; -- v_text_lines := 0; v_errcontext := 'open/fetch get_xplan'; for s in get_xplan(phv.plan_hash_value) loop -- if s.plan_table_output like 'Predicate Information %' then exit; end if; -- if v_text_lines = 0 then dbms_output.put_line('.'); dbms_output.put_line('. SQL execution plan from "'|| to_char(phv.snap_time,'MM/DD/YY HH24:MI:SS') || '" (snap #'||phv.snap_id||')'); end if; -- dbms_output.put_line(s.plan_table_output); v_text_lines := v_text_lines + 1; -- end loop; -- v_errcontext := 'delete from cstats$plan_table'; delete from cstats$plan_table where statement_id = trim(to_char(phv.plan_hash_value)); -- end if; -- end loop; --exception when others then v_errmsg := sqlerrm; raise_application_error(-20000, v_errcontext || ': ' || v_errmsg);end;/
select to_char(s.snap_time, 'DD-MON HH24:MI') snap_time, ss.executions_inc execs, ss.buffer_gets_inc/decode(ss.executions_inc,0,1,ss.executions_inc) lio_per_exec, ss.disk_reads_inc/decode(ss.executions_inc,0,1,ss.executions_inc) pio_per_exec, ss.cpu_time_inc/decode(ss.executions_inc,0,1,ss.executions_inc) cpu_per_exec, ss.elapsed_time_inc/decode(ss.executions_inc,0,1,ss.executions_inc) ela_per_execfrom stats$snapshot s, (select ss2.dbid, ss2.snap_id, ss2.instance_number, nvl(decode(greatest(ss2.executions, nvl(lag(ss2.executions) over (order by ss2.snap_id),0)), ss2.executions, ss2.executions - lag(ss2.executions) over (order by ss2.snap_id), ss2.executions), 0) executions_inc, nvl(decode(greatest(ss2.buffer_gets, nvl(lag(ss2.buffer_gets) over (order by ss2.snap_id),0)), ss2.buffer_gets, ss2.buffer_gets - lag(ss2.buffer_gets) over (order by ss2.snap_id), ss2.buffer_gets), 0) buffer_gets_inc, nvl(decode(greatest(ss2.disk_reads, nvl(lag(ss2.disk_reads) over (order by ss2.snap_id),0)), ss2.disk_reads, ss2.disk_reads - lag(ss2.disk_reads) over (order by ss2.snap_id), ss2.disk_reads), 0) disk_reads_inc, nvl(decode(greatest(ss2.cpu_time, nvl(lag(ss2.cpu_time) over (order by ss2.snap_id),0)), ss2.cpu_time, ss2.cpu_time - lag(ss2.cpu_time) over (order by ss2.snap_id), ss2.cpu_time), 0)/1000000 cpu_time_inc, nvl(decode(greatest(ss2.elapsed_time, nvl(lag(ss2.elapsed_time) over (order by ss2.snap_id),0)), ss2.elapsed_time, ss2.elapsed_time - lag(ss2.elapsed_time) over (order by ss2.snap_id), ss2.elapsed_time), 0)/1000000 elapsed_time_inc from stats$sql_summary ss2, (select distinct dbid, instance_number from stats$database_instance where instance_name like '&&V_ORACLE_SID') i where ss2.hash_value = &&V_HASH_VALUE and ss2.dbid = i.dbid and ss2.instance_number = i.instance_number) sswhere s.snap_id = ss.snap_idand s.dbid = ss.dbidand s.instance_number = ss.instance_numberorder by s.snap_time asc;
spool offset verify on echo on feedback on
SQL> @sphistory.sqlEnter the SQL statement hash value: 1234567890Enter the SID of the Oracle instance (wildcard chars permitted): PROD%
Parameters
Section titled “Parameters”- V_HASH_VALUE: Hash value of the SQL statement to analyze
- V_ORACLE_SID: Oracle instance name pattern (supports wildcards like %)
Prerequisites
Section titled “Prerequisites”- Statspack installed and configured
- CSTATS$PLAN_TABLE table must exist (created by top_stmt4_9i.sql)
- Historical Statspack snapshots available for analysis period
- SELECT privileges on STATS$ views
Required Privileges
Section titled “Required Privileges”- SELECT on STATS$SQLTEXT
- SELECT on STATS$SQL_PLAN_USAGE
- SELECT on STATS$SNAPSHOT
- SELECT on STATS$SQL_SUMMARY
- SELECT on STATS$SQL_PLAN
- SELECT on STATS$SEG_STAT_OBJ
- INSERT/DELETE on CSTATS$PLAN_TABLE
Sample Output
Section titled “Sample Output”SQL Text Display
Section titled “SQL Text Display”Text of SQL statement----------------------------------------------------------------SELECT c.customer_id, c.customer_name, o.order_date, o.total_amountFROM customers c, orders oWHERE c.customer_id = o.customer_idAND o.order_date >= :1
Execution Plan History
Section titled “Execution Plan History”.. SQL execution plan from "03/15/24 14:30:25" (snap #1245)
Plan hash value: 2084293868
------------------------------------------------------------------------------------| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|------------------------------------------------------------------------------------| 0 | SELECT STATEMENT | | 100 | 8600 | 45 (3)|| 1 | NESTED LOOPS | | 100 | 8600 | 45 (3)|| 2 | TABLE ACCESS BY INDEX ROWID | ORDERS | 50 | 2150 | 23 (0)||* 3 | INDEX RANGE SCAN | IX_ORD_DATE | 50 | | 2 (0)|| 4 | TABLE ACCESS BY INDEX ROWID | CUSTOMERS | 2 | 172 | 1 (0)||* 5 | INDEX UNIQUE SCAN | PK_CUSTOMERS | 2 | | 0 (0)|------------------------------------------------------------------------------------
Performance History
Section titled “Performance History”Snapshot Execs Avg LIO Avg PIO Avg CPU (secs) Avg Elapsed (secs)Time Per Exec Per Exec Per Exec Per Exec---------- ------- ------------ ------------ ---------------- ------------------15-MAR 09:00 45 125.5 12.3 0.05 0.0815-MAR 10:00 67 134.2 15.1 0.06 0.0915-MAR 11:00 89 142.8 18.7 0.07 0.1215-MAR 12:00 156 156.3 22.4 0.08 0.1515-MAR 13:00 234 189.7 35.6 0.12 0.23
Key Output Components
Section titled “Key Output Components”1. SQL Text Section
Section titled “1. SQL Text Section”- Complete SQL statement broken into manageable pieces
- Original formatting preserved for readability
2. Execution Plan Analysis
Section titled “2. Execution Plan Analysis”- Plan hash values showing when plans changed
- Detailed execution plans with costs and cardinality estimates
- Plan change timestamps for correlation with performance changes
- Object access methods and join strategies
3. Performance History Table
Section titled “3. Performance History Table”- Snapshot Time: When performance was measured
- Execs: Number of executions in the interval
- Avg LIO Per Exec: Average logical I/O per execution
- Avg PIO Per Exec: Average physical I/O per execution
- Avg CPU Per Exec: Average CPU time per execution (seconds)
- Avg Elapsed Per Exec: Average elapsed time per execution (seconds)
Analysis Techniques
Section titled “Analysis Techniques”Performance Trend Analysis
Section titled “Performance Trend Analysis”- Increasing CPU/Elapsed Time: Performance degradation
- Increasing I/O Per Execution: Possible plan regression
- Execution Count Spikes: Application load changes
- Plan Hash Changes: Optimizer plan instability
Plan Stability Assessment
Section titled “Plan Stability Assessment”-- Find SQL statements with multiple plansSELECT hash_value, COUNT(DISTINCT plan_hash_value) plan_countFROM stats$sql_plan_usageGROUP BY hash_valueHAVING COUNT(DISTINCT plan_hash_value) > 1ORDER BY plan_count DESC;
Performance Correlation
Section titled “Performance Correlation”- CPU vs Elapsed Time: Identify wait-bound vs CPU-bound periods
- I/O Trends: Compare logical vs physical I/O growth
- Execution Patterns: Peak vs off-peak performance differences
Common Use Cases
Section titled “Common Use Cases”-
SQL Performance Regression Analysis
- Identify when performance degraded
- Correlate with plan changes
- Track optimization efforts
-
Optimizer Plan Stability
- Monitor for unwanted plan changes
- Validate optimizer statistics refresh impact
- Plan baseline creation decisions
-
Capacity Planning
- Track resource consumption growth
- Identify scaling bottlenecks
- Plan hardware upgrades
-
Application Performance Tuning
- Isolate problematic time periods
- Correlate with application releases
- Prioritize tuning efforts
Advanced Analysis
Section titled “Advanced Analysis”Finding Hash Values
Section titled “Finding Hash Values”-- Find hash values for problematic SQLSELECT hash_value, sql_text, executions, buffer_getsFROM stats$sql_summary s, stats$sqltext tWHERE s.hash_value = t.hash_valueAND s.snap_id = (SELECT MAX(snap_id) FROM stats$snapshot)ORDER BY buffer_gets DESC;
Cross-Instance Analysis
Section titled “Cross-Instance Analysis”-- Compare performance across instancesSELECT instance_name, AVG(buffer_gets/executions) avg_lio, AVG(disk_reads/executions) avg_pioFROM stats$sql_summary s, stats$database_instance iWHERE s.dbid = i.dbidAND s.instance_number = i.instance_numberAND s.hash_value = &hash_valueGROUP BY instance_name;
Troubleshooting
Section titled “Troubleshooting”Missing CSTATS$PLAN_TABLE
Section titled “Missing CSTATS$PLAN_TABLE”-- Create the required tableCREATE TABLE cstats$plan_table ASSELECT * FROM plan_table WHERE 1=2;
No Execution Plans Displayed
Section titled “No Execution Plans Displayed”- Verify STATS$SQL_PLAN_USAGE has data
- Check if SQL was executed during plan capture periods
- Ensure sufficient Statspack retention
Performance Data Inconsistencies
Section titled “Performance Data Inconsistencies”- Check for Statspack snapshot gaps
- Verify instance restarts during analysis period
- Confirm consistent snapshot intervals
Integration with Modern Tools
Section titled “Integration with Modern Tools”Migration to AWR
Section titled “Migration to AWR”This script concept can be adapted for AWR:
-- AWR equivalent query structureSELECT snap_id, executions_delta, buffer_gets_deltaFROM dba_hist_sqlstatWHERE sql_id = '&sql_id'ORDER BY snap_id;
SQL Plan Management
Section titled “SQL Plan Management”Use insights from this analysis to:
- Create SQL plan baselines for stable plans
- Identify candidates for SQL profiles
- Monitor plan regression after changes
Best Practices
Section titled “Best Practices”-
Regular Monitoring
- Run for critical SQL statements weekly
- Establish performance baselines
- Track trends over time
-
Analysis Correlation
- Correlate with system load changes
- Check for statistics refresh impacts
- Review alongside application changes
-
Documentation
- Save reports for historical reference
- Document plan changes and their causes
- Maintain performance baseline records