Chapter 09 · Optimizer Internals, Statistics, Execution Plans, and SQL Tuning

EXPLAIN PLAN vs Runtime Plans, DBMS_XPLAN, Predicate Information, and Row-Source Metrics

Separate EXPLAIN PLAN from the child cursor that actually ran, then compare E-Rows, A-Rows, Starts, Buffers, predicates, and row-source metrics.

Advanced120–140 minutesEstimated vs executed-plan labDBMS_XPLAN.DISPLAY vs DISPLAY_CURSORExact fixed-view privileges · no AWR/SQL Monitor mandatoryLast reviewed: August 2026

Learning outcomes

A developer pastes an EXPLAIN PLAN into an incident and says it proves the slow request used a hash join that returned 10 rows. It proves neither claim. EXPLAIN PLAN asks the optimizer to estimate a plan; the application may have executed another child cursor with different binds, statistics, optimizer environment, or adaptive choices. This lesson moves from predicted shape to executed row-source evidence.

01

Distinguish PLAN_TABLE output from a child cursor in the cursor cache.

02

Collect per-row-source statistics with GATHER_PLAN_STATISTICS instead of enabling broad instrumentation casually.

03

Use DBMS_XPLAN.DISPLAY_CURSOR to compare E-Rows, A-Rows, Starts, Buffers, predicates, and memory/I/O data.

04

Treat estimate/actual divergence as a diagnostic lead rather than a final root cause.

05

Use exact fixed-view privileges and keep AWR/SQL Monitor outside the mandatory path.

Version, tooling, and licensing baseline

Mandatory work targets a disposable ServiceHub schema in Oracle AI Database Free 26ai. This chapter was reviewed against RU 23.26.3 (July 2026), SQL Developer 26.2 (July 14, 2026), and SQLcl 26.2.1 (August 10, 2026). Free is limited to 2 foreground CPUs, 2 GB combined SGA/PGA RAM, and 12 GB user data and receives no patches or Oracle Support service requests. SQL Plan Management is included in Free and does not require Diagnostics Pack or Tuning Pack. Diagnostics Pack and Tuning Pack are also included in Free, but on EE/EE-ES they are extra-cost packs; Tuning Pack requires Diagnostics Pack there. AWR, ASH, SQL Monitor, SQL Tuning Advisor, and SQL Profiles are therefore never treated as universally licensed production defaults.

1. EXPLAIN PLAN answers “what would the optimizer choose?”

EXPLAIN PLAN FOR writes estimated plan rows to a plan table. It does not execute the query, has no actual row counts, and need not match a child cursor already used by an application. It remains useful for design exploration and as a fallback when cursor privileges are unavailable.

sql · estimated plan only
EXPLAIN PLAN SET STATEMENT_ID='SH09L3_EXPLAIN' FORSELECT c.case_id,c.amount FROM servicehub_plan_case cWHERE c.status_code='ESCALATED' AND c.region_code='BAKU';SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(NULL,'SH09L3_EXPLAIN','TYPICAL +ROWS +PREDICATE'));

2. DISPLAY_CURSOR shows the loaded child cursor

DBMS_XPLAN.DISPLAY_CURSOR formats a plan from the cursor cache. Runtime I/O/row-source statistics are available when collected, for example with /*+ GATHER_PLAN_STATISTICS */ or STATISTICS_LEVEL=ALL. The hint is preferable for a focused diagnostic because setting ALL broadly can add overhead.

sql · execute one statement with row-source statistics
SELECT /*+ GATHER_PLAN_STATISTICS */ case_id,amountFROM servicehub_plan_caseWHERE status_code='ESCALATED' AND region_code='BAKU'ORDER BY case_id;SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST +PREDICATE +NOTE'));

E-Rows are optimizer estimates. A-Rows are rows produced by the operation for the measured execution. Starts counts operation starts. Buffers measures logical buffer activity, not just physical reads. MEMSTATS can expose workarea memory/spill behavior for suitable operators when statistics are available.

3. Use exact fixed-view privileges

Oracle documents that DISPLAY_CURSOR requires read/select access to V$SQL, V$SQL_PLAN, V$SQL_PLAN_STATISTICS_ALL, and V$SESSION. For the disposable course PDB, a DBA can grant only those underlying fixed views rather than a broad catalog role.

sql · privileged setup in the course PDB
GRANT SELECT ON V_$SQL TO servicehub_owner;GRANT SELECT ON V_$SQL_PLAN TO servicehub_owner;GRANT SELECT ON V_$SQL_PLAN_STATISTICS_ALL TO servicehub_owner;GRANT SELECT ON V_$SESSION TO servicehub_owner;
sql · cleanup grants after the diagnostics modules if desired
REVOKE SELECT ON V_$SQL FROM servicehub_owner;REVOKE SELECT ON V_$SQL_PLAN FROM servicehub_owner;REVOKE SELECT ON V_$SQL_PLAN_STATISTICS_ALL FROM servicehub_owner;REVOKE SELECT ON V_$SESSION FROM servicehub_owner;

If these privileges are not available, keep the conceptual lab with DBMS_XPLAN.DISPLAY and have a DBA capture the executed cursor. Do not jump to AWR: on EE/EE-ES AWR/ASH require Diagnostics Pack.

4. Find the first material estimate error

If a filter estimates 10 rows and actually produces 8,000, every downstream join/sort is costed with a distorted input. Diagnose from the leaves upward and identify where E-Rows first diverge materially from A-Rows. Common causes include skew, correlation, stale statistics, expressions without representative statistics, bind-value sensitivity, and data changes.

Wrong repair

A bad-looking join operator is often downstream of a bad cardinality estimate. Fix the model first when possible instead of pinning a different operator.

5. Wrong evidence: EXPLAIN PLAN plus stopwatch

Elapsed time from one manual execution is affected by cache, client fetching, load, and network. EXPLAIN PLAN is estimated. The repair is to execute the exact SQL with representative binds, capture the matching child cursor with scoped statistics, and compare row-source metrics.

6. Hands-on lab: manufacture and repair an estimate problem

sql · setup with deliberately simple statistics
DROP TABLE servicehub_plan_case IF EXISTS PURGE;CREATE TABLE servicehub_plan_case(case_id NUMBER PRIMARY KEY,status_code VARCHAR2(12) NOT NULL,region_code VARCHAR2(12) NOT NULL,amount NUMBER(10,2) NOT NULL);INSERT INTO servicehub_plan_caseSELECT LEVEL,CASE WHEN LEVEL<=9900 THEN 'CLOSED' ELSE 'ESCALATED' END,CASE WHEN MOD(LEVEL,10)=0 THEN 'BAKU' ELSE 'NORTH' END,MOD(LEVEL*47,10000)/10FROM dual CONNECT BY LEVEL<=10000;CREATE INDEX sh09_l3_status_region_ix ON servicehub_plan_case(status_code,region_code);BEGIN DBMS_STATS.GATHER_TABLE_STATS(USER,'SERVICEHUB_PLAN_CASE',cascade=>TRUE,method_opt=>'FOR ALL COLUMNS SIZE 1'); END;/
sql · measure before and after AUTO histogram selection
SELECT /*+ GATHER_PLAN_STATISTICS */ case_id,amountFROM servicehub_plan_case WHERE status_code='ESCALATED' AND region_code='BAKU';SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST +PREDICATE +NOTE'));BEGIN DBMS_STATS.GATHER_TABLE_STATS(USER,'SERVICEHUB_PLAN_CASE',cascade=>TRUE,method_opt=>'FOR ALL COLUMNS SIZE AUTO'); END;/SELECT /*+ GATHER_PLAN_STATISTICS */ case_id,amountFROM servicehub_plan_case WHERE status_code='ESCALATED' AND region_code='BAKU';SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST +PREDICATE +NOTE'));DROP TABLE servicehub_plan_case PURGE;

Do not require a specific plan change. Compare the estimates, actual rows, buffers, and predicates; the optimizer is allowed to keep the same plan if it remains cheapest.

7. Production judgment

Use EXPLAIN PLAN for estimated design work and DISPLAY_CURSOR for actual loaded-cursor evidence. Capture the right SQL ID/child where multiple children exist. Scoped row-source instrumentation is usually more responsible than enabling global ALL statistics just to inspect one SQL. The mandatory path does not require AWR, ASH, SQL Monitor, Diagnostics Pack, or Tuning Pack.

Lesson 4 uses these metrics to explain why nested loops, hash joins, sort merge joins, bloom filters, parallel operations, and adaptive branches fit different row-flow shapes.

Check your understanding

  1. What does EXPLAIN PLAN not do?
  2. What does A-Rows represent?
  3. How do you collect row-source stats for one statement?
  4. Which fixed views are needed for DISPLAY_CURSOR?
  5. What should a large E-Rows/A-Rows gap trigger?
Review the answers

It does not execute the SQL or prove the application used that child plan.

Actual rows produced by a row source for the reported execution(s).

Use the GATHER_PLAN_STATISTICS hint.

V$SQL, V$SQL_PLAN, V$SQL_PLAN_STATISTICS_ALL, and V$SESSION.

Investigation of the first estimate error and its statistics/data/predicate cause.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.