Most of the time you fix a bad DB2 access path with statistics, SQL, and indexes. When that is not enough—or when a release migration changed a path you cannot afford to lose—you can give the optimizer an optimization hint. This page covers PLAN_TABLE OPTHINT access path hints, statement-level hints (the z/OS “optimization profile” repository), BIND QUERY, and when not to use any of them.
The optimizer is cost-based. A hint is you saying “use this path (or these options) anyway.” That is powerful and dangerous. A hinted index that is later dropped, or a hinted nested loop after the inner table grows 100×, will not magically stay cheap. Treat hints as documented exceptions, not as a substitute for RUNSTATS.
Subsystem parameter OPTHINTS must be YES or Db2 ignores PLAN_TABLE hint names. Statement-level hints live in the access path repository after BIND QUERY; they still depend on a supported Db2 level and matching SQL text.
Related but different: APREUSE on BIND/REBIND applies the previous package path as an internal hint. Prefer APREUSE and PLANMGMT when you only want stability. Use the techniques on this page when you need a path that is not “whatever we bound last week,” or when dynamic SQL must pick a named path.
| Kind | How you define it | Scope |
|---|---|---|
| PLAN_TABLE / OPTHINT | Name rows in PLAN_TABLE; BIND OPTHINT or SET CURRENT OPTIMIZATION HINT | Authid’s PLAN_TABLE; static package or dynamic session |
| Statement-level access path | DSN_USERQUERY_TABLE + PLAN_TABLE + BIND QUERY | Package version, package, or system-wide by SQL text |
| Statement-level options | DSN_USERQUERY_TABLE only (REOPT, degree, star join, …) + BIND QUERY | Same scopes; unique QUERYNO |
| Selectivity override | DSN_PREDICAT_TABLE / DSN_PREDICATE_SELECTIVITY + BIND QUERY | Matching statement text |
When both a PLAN_TABLE hint and a statement-level hint apply to the same statement, PLAN_TABLE hints take precedence. Do not maintain two conflicting stories for one SQL text.
The older, well-known method: capture a good explain, then tell Db2 to reuse those PLAN_TABLE rows as the path.
123456789101112131415161718-- After EXPLAIN, tag the path UPDATE PLAN_TABLE SET OPTHINT = 'GOODIX1' WHERE QUERYNO = 1001 AND EXPLAIN_TIME = '2026-08-13-10.00.00.000000'; -- Dynamic SQL in the same SQLID: SET CURRENT OPTIMIZATION HINT = 'GOODIX1'; EXPLAIN PLAN SET QUERYNO = 1002 FOR SELECT EMPNO, LASTNAME FROM HR.EMPLOYEE WHERE WORKDEPT = 'A00'; SELECT HINT_USED, ACCESSTYPE, ACCESSNAME, MATCHCOLS FROM PLAN_TABLE WHERE QUERYNO = 1002 ORDER BY QBLOCKNO, PLANNO;
123REBIND PACKAGE (HR.EMPPGM.(V2)) OPTHINT(GOODIX1) EXPLAIN(YES)
Weak spots of PLAN_TABLE hints:
That is why IBM later added statement-level hints that match SQL text.
On Db2 for Linux, UNIX, and Windows, an optimization profile is often an XML document. On Db2 for z/OS, the equivalent “profile” is the access path repository: you load input tables and run BIND QUERY. People still say “optimization profile” for this set of objects.
Create the user query table (sample DDL is in SDSNSAMP with the other EXPLAIN tables). Each row identifies a statement:
Runtime / option hints need only DSN_USERQUERY_TABLE (unique QUERYNO).Access path hints also need correlated PLAN_TABLE rows with the same QUERYNO. Selectivity overrides use DSN_PREDICAT_TABLE and DSN_PREDICATE_SELECTIVITY so you can tell the optimizer “this predicate is more selective than SYSCOLDIST says.”
123456BIND QUERY LOOKUP(NO) -- Reads DSN_USERQUERY_TABLE (+ PLAN_TABLE / predicate tables) -- Inserts SYSIBM.SYSQUERY and SYSIBM.SYSQUERYOPTS FREE QUERY QUERYID(integer) -- Removes a repository hint
After a successful BIND QUERY, delete or disable the input row in DSN_USERQUERY_TABLE so a later BIND QUERY does not overlay the repository by accident. Validate with EXPLAIN: look for ACCESSPATH_HINT, SELECTVTY_OVERRIDE, or OPTION_OVERRIDE style indicators in EXPLAIN output for your version, and for HINT_USED on PLAN_TABLE.
Static statements pick up statement-level hints at REBIND. Dynamic statements pick them up at prepare. Scope search is typically package version, then package, then system-wide—most specific wins among statement-level hints.
You generally cannot hint “run this 50% faster.” You hint how to run. Measure with accounting and EXPLAIN cost after the hint sticks.
Virtual indexes (DSN_VIRTUAL_INDEXES) are a what-if input for EXPLAIN, not a production hint. Do not confuse “pretend this index exists” with “force this path in production.”
The optimizer is a GPS that picks a driving route from traffic reports (statistics). A hint is you writing on a sticky note “always take Maple Street.” OPTHINT is the name on that sticky note, stuck to a map you already drew (PLAN_TABLE). Statement-level hints are a rule at the city hall: “any car whose trip description matches this sentence must take Maple Street,” even if the car did not bring your old map. BIND QUERY is filing that city-hall rule. If Maple Street is closed next year and you forget to peel off the sticky note, you still try to drive it—so check the note when roads change.
1. What ZPARM must be YES before PLAN_TABLE optimization hints work?
2. How do you apply a PLAN_TABLE hint to static SQL?
3. Why are statement-level hints often preferred over PLAN_TABLE QUERYNO hints?
4. What does BIND QUERY do?
5. If both a PLAN_TABLE hint and a statement-level hint apply, which wins?