Close Menu
  • Home
  • Oracle
    • ASM
    • Data Guard
    • OEM
    • RAC
  • MongoDB
  • Performance
  • Python
  • Shell Script
  • Tools
  • Troubleshooting
Search

Oracle SQL Patch: Inject Optimizer Hints Without Changing Code

2026-06-13 Performance By Henrique

Oracle ACCOUNT_STATUS: What Each Value Means

2026-06-04 Oracle By Henrique

Oracle RAC 12.2 on VMware Workstation- Post 5: Final Validation and Quick Reference

2026-05-11 Oracle By Henrique
YouTube LinkedIn RSS
  • Home
  • About
  • Contact
  • Legal
    • Cookie Policy
    • Disclaimer
    • Privacy Policy
    • Terms of Use
  • RSS
  • English
    • Portuguese (Brazil)
Execute StepExecute Step
YouTube LinkedIn RSS
  • Home
  • Oracle
    • ASM
    • Data Guard
    • OEM
    • RAC
  • MongoDB
  • Performance
  • Python
  • Shell Script
  • Tools
  • Troubleshooting
Execute StepExecute Step
Home » Oracle SQL Patch: Inject Optimizer Hints Without Changing Code
Performance

Oracle SQL Patch: Inject Optimizer Hints Without Changing Code

HenriqueBy Henrique2026-06-139 Mins Read
Share
Facebook Twitter LinkedIn Pinterest Email Telegram WhatsApp

This post is also available in: Português (Portuguese (Brazil))

You inherited a query with PARALLEL(16) baked into the code, or the opposite: you need to force parallelism on a query that runs serial, and you can’t touch the application. Vendor code, deploys that strip inline SQL comments, a ticket that takes months to become a release. The SQL Patch exists for exactly this: it injects hints into a specific statement without changing a single line of code.

This post walks through when a SQL Patch is the right tool, how it compares to Baselines and Profiles, and a full 19c lab that forces parallelism on a serial query, with the real output of every step.

What a SQL Patch is (and isn’t)

A SQL Patch is a set of hints the optimizer applies to a statement identified by its text signature. It does not freeze a whole plan: it only adds the hint constraint and lets the CBO pick the best plan within it.

Three things that matter in practice:

  • No Tuning Pack required (unlike a SQL Profile).
  • It survives application deploys that don’t preserve inline comments.
  • It is lightweight: it stores hints, not a full plan with an outline.

When a SQL Patch is the right call

  • The application code cannot be changed.
  • You already tried a SQL Plan Baseline and it won’t reproduce (REPRODUCED = NO).
  • You need something simple: force one specific hint, without pinning the entire plan.
  • You want flexibility: the CBO still chooses the plan, but inside the hint’s constraint.

SQL Patch vs Baseline vs Profile vs inline hint

ApproachWhat it doesCost / risk
SQL PatchInjects specific hintsLightweight. Always applies the hint. No Tuning Pack
SQL Plan BaselineTries to recreate a full planHeavy. Can fail to reproduce and bail out
SQL ProfileAdjusts estimates (cardinality, cost)Needs Tuning Pack. Does not guarantee the plan
Inline hintHint inside the codeGone on the first deploy that rewrites the SQL

Bottom line: a Baseline is heavy and may not reproduce; a Profile touches estimates and needs a license; an inline hint dies at deploy time. The Patch is direct, explicit, and persistent.

Lab: force PARALLEL without changing the code

⚠️ Prerequisites: Enterprise Edition (Parallel Query is an EE feature), parallel_max_servers > 0 (the default is fine), and privileges to run DBMS_SQLDIAG. Scenario: Oracle 19c, Single Instance.

1. Test data

A table with no degree of parallelism set (DEGREE = 1) and no hint at all.

DROP TABLE t_lab_px PURGE;

CREATE TABLE t_lab_px AS
SELECT level AS id,
       MOD(level, 1000) AS grp,
       RPAD('x', 50, 'x') AS pad
FROM   dual
CONNECT BY level <= 3000000;

EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'T_LAB_PX');

SELECT degree FROM user_tables WHERE table_name = 'T_LAB_PX';
-- DEGREE = 1

⚠️ Hit ORA-30009 (Not enough memory for CONNECT BY)? On sessions with a tight PGA, generating 3 million levels at once blows up. Swap the generator for two small CONNECT BY blocks in a cross join, which never hold everything in the PGA: “sql CREATE TABLE t_lab_px AS WITH gen AS ( SELECT /+ materialize / level AS n FROM dual CONNECT BY level <= 2000 ) SELECT ROWNUM AS id, MOD(ROWNUM, 1000) AS grp, RPAD('x', 50, 'x') AS pad FROM gen a, gen b WHERE ROWNUM <= 3000000; “

2. Baseline: the plan starts serial

SELECT /*+ qb_name(main) */ grp, COUNT(*)
FROM   t_lab_px t
GROUP  BY grp;

SELECT sql_id, child_number
FROM   v$sql
WHERE  sql_text LIKE 'SELECT /*+ qb_name(main) */ grp%'
AND    sql_text NOT LIKE '%v$sql%';
SQL_ID        CHILD_NUMBER
------------- ------------
3w40z8nk3h5g5            0
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('3w40z8nk3h5g5', NULL, 'BASIC +NOTE'));
Plan hash value: 1056803887

---------------------------------------
| Id  | Operation          | Name     |
---------------------------------------
|   0 | SELECT STATEMENT   |          |
|   1 |  HASH GROUP BY     |          |
|   2 |   TABLE ACCESS FULL| T_LAB_PX |
---------------------------------------

No parallelism. A plain serial TABLE ACCESS FULL.

💡 The qb_name(main) is on purpose: naming the query block keeps the patch hint more readable and stable than relying on the auto-generated SEL$1.

3. Create the SQL Patch injecting PARALLEL

In 19c, CREATE_SQL_PATCH is a function, not a procedure: the return value has to go into a variable.

SET SERVEROUTPUT ON
DECLARE
  v_patch VARCHAR2(128);
BEGIN
  v_patch := DBMS_SQLDIAG.CREATE_SQL_PATCH(
               sql_id      => '3w40z8nk3h5g5',
               hint_text   => 'PARALLEL(4)',
               name        => 'PX_LAB_PATCH',
               description => 'Inject PARALLEL(4) without changing the code',
               category    => 'DEFAULT',
               validate    => TRUE);
  DBMS_OUTPUT.PUT_LINE('Patch created: ' || v_patch);
END;
/
Patch created: PX_LAB_PATCH
PL/SQL procedure successfully completed.
SELECT name, status, force_matching, sql_text
FROM   dba_sql_patches
WHERE  name = 'PX_LAB_PATCH';
NAME           STATUS   FOR SQL_TEXT
-------------- -------- --- -------------------------------------------
PX_LAB_PATCH   ENABLED  NO  SELECT /*+ qb_name(main) */ grp, COUNT(*)...

💡 To target a specific table instead of the whole statement, use the object form of the hint: PARALLEL(t@main 4), in the PARALLEL(alias@query_block degree) format.

4. Run it again and confirm the parallel plan

The sql_id does not change: the text is identical, so only a new child cursor is born.

SELECT /*+ qb_name(main) */ grp, COUNT(*)
FROM   t_lab_px t
GROUP  BY grp;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('3w40z8nk3h5g5', NULL, 'BASIC +NOTE'));
Plan hash value: 142943352

---------------------------------------------
| Id  | Operation                | Name     |
---------------------------------------------
|   0 | SELECT STATEMENT         |          |
|   1 |  PX COORDINATOR          |          |
|   2 |   PX SEND QC (RANDOM)    | :TQ10001 |
|   3 |    HASH GROUP BY         |          |
|   4 |     PX RECEIVE           |          |
|   5 |      PX SEND HASH        | :TQ10000 |
|   6 |       HASH GROUP BY      |          |
|   7 |        PX BLOCK ITERATOR |          |
|   8 |         TABLE ACCESS FULL| T_LAB_PX |
---------------------------------------------

Note
-----
   - Degree of Parallelism is 4 because of hint
   - SQL patch "PX_LAB_PATCH" used for this statement

Two lines in the Note section close the case: the degree of parallelism is now 4 because of the hint, and the patch was actually applied.

5. Confirm runtime usage

SELECT sql_id, child_number, sql_patch
FROM   v$sql
WHERE  sql_id = '3w40z8nk3h5g5'
AND    sql_patch IS NOT NULL;
SQL_ID        CHILD_NUMBER SQL_PATCH
------------- ------------ ------------
3w40z8nk3h5g5            0 PX_LAB_PATCH

Day-to-day administration

-- List
SELECT name, status, force_matching FROM dba_sql_patches;

-- Disable without dropping
EXEC DBMS_SQLDIAG.ALTER_SQL_PATCH('PX_LAB_PATCH', 'STATUS', 'DISABLED');

-- Re-enable
EXEC DBMS_SQLDIAG.ALTER_SQL_PATCH('PX_LAB_PATCH', 'STATUS', 'ENABLED');

-- Drop
EXEC DBMS_SQLDIAG.DROP_SQL_PATCH('PX_LAB_PATCH');

-- Confirm usage at runtime
SELECT sql_id, sql_patch FROM v$sql WHERE sql_patch IS NOT NULL;

Gotchas worth gold

The patch is tied to the text signature. If the application sends changing literals (a different sql_id on every run), a patch keyed by sql_id matches only that one variant. For the rest, it simply won’t apply.

Force matching does not come through CREATE_SQL_PATCH. The public DBMS_SQLDIAG.CREATE_SQL_PATCH function accepts only sql_id (or sql_text), hint_text, name, description, category, and validate. There is no force_match parameter here. The force_match that does exist belongs to a different function, DBMS_SQLDIAG.ACCEPT_SQL_PATCH, used in the SQL Repair Advisor flow. To force-match a patch created by CREATE_SQL_PATCH, the path is the staging-table workaround (create the staging table with CREATE_STGTAB_SQLPATCH, pack it with PACK_STGTAB_SQLPATCH, edit sqlflags and the signature in the table, then re-import). It is an advanced technique, not a simple parameter.

PARALLEL hints behave their own way. IGNORE_OPTIM_EMBEDDED_HINTS and OPTIMIZER_IGNORE_HINTS do not knock out an embedded PARALLEL. For the reverse case, removing hardcoded parallelism, the path that works is injecting opt_param('optimizer_ignore_parallel_hints' 'true') via a patch (available from 18c onward).

CREATE_SQL_PATCH changed nature in 12.2. If you come from the DBMS_SQLDIAG_INTERNAL.I_CREATE_PATCH era, remember: in 19c it is a function with a return value, so capture it in a variable.

Conclusion

When you can’t touch the code but need to control the plan, the script is:

  1. Identify the sql_id of the problem query.
  2. Capture the current plan as a baseline (DBMS_XPLAN.DISPLAY_CURSOR).
  3. Create the patch with the desired hint via DBMS_SQLDIAG.CREATE_SQL_PATCH (remember: it’s a function in 19c).
  4. Run again and validate through the plan’s Note section and v$sql.sql_patch.
  5. If literals vary, evaluate force matching through the staging table, not a parameter.
  6. Document the patch and keep ALTER and DROP handy to roll back.

References

  • Oracle Database 19c PL/SQL Packages Reference: DBMS_SQLDIAG. https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_SQLDIAG.html
  • SQLMaria (Nigel Bayliss): How to use a SQL Plan Baseline or a SQL Patch to add Optimizer hints. https://sqlmaria.com/2020/02/25/how-to-use-a-sql-plan-baseline-or-a-sql-patch-to-add-optimizer-hints/
  • Jonathan Lewis: sql_patch. https://jonathanlewis.wordpress.com/2019/08/21/sql_patch/
  • Jonathan Lewis: force_match patch. https://jonathanlewis.wordpress.com/2023/08/19/force_match-patch/
dbms_sqldiag execution-plan lab parallel
Share. Facebook Twitter Pinterest LinkedIn Tumblr Email WhatsApp
Previous ArticleOracle ACCOUNT_STATUS: What Each Value Means

Related Posts

Oracle

Oracle ACCOUNT_STATUS: What Each Value Means

2026-06-04
Read More
Oracle

Oracle RAC 12.2 on VMware Workstation- Post 5: Final Validation and Quick Reference

2026-05-11
Read More
Oracle

Oracle RAC 12.2 on VMware Workstation – Post 4: Oracle Database Installation and DBCA

2026-05-11
Read More
0 0 votes
Article Rating
Subscribe
Login
Notify of
guest

guest

0 Comments
Oldest
Newest Most Voted
Demo
Follow Me
  • Email
  • GitHub
  • LinkedIn
  • RSS
  • YouTube

INS-06006 – Passwordless SSH Connectivity Not Set Up

2026-02-2615 Views

INS-08101- How to Fix “Unexpected Error at supportedOSCheck” When Installing Oracle Grid Infrastructure on RHEL 8

2026-03-0411 Views

ORA-29548- How to Fix “Java System Class Reported” in Oracle Database

2026-03-0510 Views
Demo
Blogroll
  • oravirt
Execute Step
YouTube LinkedIn RSS
  • Home
  • About
  • Contact
  • RSS
  • English
    • Português (Portuguese (Brazil))
© 2026 ExecuteStep. Designed by ThemeSphere.

Type above and press Enter to search. Press Esc to cancel.

wpDiscuz
Ad Blocker Enabled!
Ad Blocker Enabled!
Our website is made possible by displaying online advertisements to our visitors. Please support us by disabling your Ad Blocker.