OracleDBconcept

Oracle Optimizer: A Practical Guide For a Good Execution Plan

Oracle Optimizer: A Practical Guide For a Good Execution Plan 

 

Have you ever seen a critical SQL query run lightning fast one day and unbelievably slow the next—without a single code change?

The usual suspect: the Oracle Optimizer choosing a different execution plan.

The optimizer constantly adapts based on new statistics, system load, and environment changes. But when performance inconsistency becomes a risk, you need stability—especially in production.

That’s where SQL Plan Management (SPM) comes in. SPM allows you to capture a known-good execution plan and ensure Oracle uses it reliably every time.

This guide walks you through identifying, capturing, verifying, enforcing, migrating, and maintaining SQL Plan Baselines so your queries stay predictable and fast.

 

1. Pre-Checks: Make Sure SPM Is Ready

Before capturing a SQL Plan Baseline, verify these prerequisites.Check if SPM is enabled

SHOW PARAMETER optimizer_use_sql_plan_baselines;

 Ensure required privileges are granted

  • ADMINISTER SQL MANAGEMENT OBJECT — to run DBMS_SPM
  • SELECT_CATALOG_ROLE — to query AWR views (e.g., DBA_HIST_SQLSTAT)

Ask your DBA to grant them if needed.

 

2. Identify the SQL and Its “Golden” Execution Plan

Every SQL statement has:

a SQL_ID

one or more plans identified by PLAN_HASH_VALUE (PHV)

If the good plan was used recently (in memory), check the cursor cache  — Find a recent plan in the cursor cache

SELECT
sql_id,
plan_hash_value,
parsing_schema_name,
executions
FROM
gv$sqlarea
WHERE
sql_id = ‘&SQL_ID’;

 

 If the good plan is older, search AWR history — Find a historical plan in AWR

SELECT
snap_id,
sql_id,
plan_hash_value,
elapsed_time_delta,
executions_delta
FROM
dba_hist_sqlstat
WHERE
sql_id = ‘&SQL_ID’
ORDER BY
elapsed_time_delta DESC; — Find the fastest executions

Once you have your SQL_ID and PLAN_HASH_VALUE, you’re ready to create the baseline.

 

3. Create the SQL Plan Baseline

You can load a baseline either from:

the cursor cache (preferred when available), or

AWR (when the plan is no longer in memory)

Option A — Load from the Cursor Cache (Fastest Method)

DECLARE
l_loaded NUMBER;
BEGIN
l_loaded := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(
sql_id => ‘&SQL_ID’,
plan_hash_value => &PLAN_HASH_VALUE,
enabled => ‘YES’
);

DBMS_OUTPUT.PUT_LINE(‘Baselines loaded: ‘ || l_loaded);
END;
/

Option B — Load from AWR (When the Plan Is Not in Cache)

1️⃣ Identify snapshot range. Find the begin and end snapshot IDs

SELECT
MIN(snap_id) begin_snap,
MAX(snap_id) end_snap
FROM
dba_hist_snapshot
WHERE
end_interval_time BETWEEN TO_DATE(‘&START_TIME’, ‘YYYY-MM-DD HH24:MI’)
AND TO_DATE(‘&END_TIME’, ‘YYYY-MM-DD HH24:MI’);

 

2️⃣ Load plan from AWR . Load the baseline from the AWR snapshot window

DECLARE
l_loaded NUMBER;
BEGIN
l_loaded := DBMS_SPM.LOAD_PLANS_FROM_AWR(
begin_snap => &BEGIN_SNAP,
end_snap => &END_SNAP,
sql_id => ‘&SQL_ID’,
plan_hash_value => &PLAN_HASH_VALUE,
enabled => ‘YES’
);

DBMS_OUTPUT.PUT_LINE(‘Baselines loaded: ‘ || l_loaded);
END;
/

4. Verify the Baseline and (Optionally) Fix It 🔍 Check the newly created baseline

SELECT
sql_handle,
plan_name,
enabled,
accepted,
fixed,
created
FROM
dba_sql_plan_baselines
WHERE
sql_text LIKE ‘%<unique fragment of the SQL>%’;

By default:

ENABLED = YES

ACCEPTED = YES

This means the optimizer can choose it when appropriate.

💡 Best Practice:

Do not fix a plan immediately. Let it run for some time.
Fix it only when you are 100% confident it performs best under all conditions.

📌 Fix the plan (optional)

DECLARE
l_out PLS_INTEGER;
BEGIN
l_out := DBMS_SPM.ALTER_SQL_PLAN_BASELINE(
sql_handle => ‘&SQL_HANDLE’,
plan_name => ‘&PLAN_NAME’,
attribute_name => ‘fixed’,
attribute_value => ‘YES’
);
END;
/

 

5. Validate That the Baseline Is Being Used

Run the query again and check the execution plan:

— Show the executed plan and check the notes

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, ‘BASIC +NOTE’));

You should see a line like:

Note
—–
– SQL plan baseline “PLAN_NAME_HERE” used for this statement

You can also check via:

SELECT sql_id, sql_plan_baseline, plan_hash_value
FROM gv$sql
WHERE sql_id = ‘&SQL_ID’;

 

6. Migrate Baselines Between Environments (Optional)

To move a baseline from UAT → PROD, use SPM staging tables.

📦 Pack baseline in source environment

BEGIN
— Create the staging table
DBMS_SPM.CREATE_STGTAB_BASELINE(table_name => ‘SPM_STAGE’, schema_name => ‘APPS’);

— Pack the desired baseline into the table
DBMS_SPM.PACK_STGTAB_BASELINE(
table_name => ‘SPM_STAGE’,
schema_name => ‘APPS’,
sql_handle => ‘&SQL_HANDLE’
);
END;
/

Export the SPM_STAGE table using Data Pump and import it into the target DB.

📦 Unpack in target environment
BEGIN
DBMS_SPM.UNPACK_STGTAB_BASELINE(
table_name => ‘SPM_STAGE’,
schema_name => ‘APPS’
);
END;
/

 

7. Baseline Maintenance — Disable or Drop

🚫 Disable a baseline without deleting it
DECLARE
n PLS_INTEGER;
BEGIN
n := DBMS_SPM.ALTER_SQL_PLAN_BASELINE(
sql_handle => ‘&SQL_HANDLE’,
plan_name => ‘&PLAN_NAME’,
attribute_name => ‘enabled’,
attribute_value => ‘NO’
);
END;
/

❌ Drop a baseline permanently
DECLARE
n PLS_INTEGER;
BEGIN
n := DBMS_SPM.DROP_SQL_PLAN_BASELINE(
sql_handle => ‘&SQL_HANDLE’,
plan_name => ‘&PLAN_NAME’
);
END;
/

 

8. When to Use SQL Profiles Instead of Baselines

SPM requires exact SQL text matching, which may not work if your application uses varying literals.

In such cases, a SQL Profile may be more effective—it applies optimizer hints to guide plan selection while allowing literal variations.

📌 Get outline hints from the good plan

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(‘&SQL_ID’, NULL, ‘ADVANCED’));
— Copy the hints from the “Outline Data” section

📌 Import the SQL Profile
DECLARE
h SYS.SQLPROF_ATTR;
BEGIN
h := SYS.SQLPROF_ATTR(
‘USE_HASH_AGGREGATION(@SEL$1)’,
‘LEADING(@”SEL$1″ “T1″@”SEL$1” “T2″@”SEL$1”)’,
‘INDEX_RS_ASC(“T1″@”SEL$1” “T1_IDX”)’
— Paste all other outline hints here
);

DBMS_SQLTUNE.IMPORT_SQL_PROFILE(
sql_text => q'[ PASTE THE EXACT SQL TEXT HERE ]’,
profile => h,
name => ‘PROF_FIX_MY_QUERY’,
force_match => TRUE, — Set TRUE to match queries with different literals
replace => TRUE
);
END;
/

SQL Profiles give flexibility but less determinism. Prefer SPM baselines for strict plan stability.

 

9. Practical Tips & Common Pitfalls

🔸 Exact SQL Text Matters

Even a space or case change can prevent baseline matching.

🔸 Watch for Statistics Drift

Old or stale statistics can cause cardinality shifts—even with a baseline.

🔸 Bind Peeking Still Happens

If bind sensitivity causes unstable plans, avoid fixing the baseline too early.

🔸 If the baseline isn’t being used, check:

optimizer_use_sql_plan_baselines = TRUE

 

 

    About Syed Saad

    With 13 years of experience as a certified and skilled Oracle Database Administrator, I possess the expertise to handle various levels of database maintenance tasks and proficiently perform Oracle updates. Throughout my career, I have honed my analytical abilities, enabling me to swiftly diagnose and resolve issues as they arise. I excel in planning and executing special projects within time-sensitive environments, showcasing exceptional organizational and time management skills. My extensive knowledge encompasses directing, coordinating, and exercising authoritative control over all aspects of planning, organization, and successful project completions. Additionally, I have a strong aptitude for resolving customer relations matters by prioritizing understanding and effective communication. I am adept at interacting with customers, vendors, and management, ensuring seamless communication and fostering positive relationships.

    Check Also

    1

    Creating LVM In Oracle Linux

    Creating LVM In Oracle Linux   1. Ask the System Admin to add a raw …

    Leave a Reply