When a query is slow, the first question is not “add an index?”—it is “what path did DB2 choose?” EXPLAIN writes that answer into tables you can SELECT. This page is the map: what EXPLAIN is, how to run it for static and dynamic SQL, and how to read the first PLAN_TABLE columns. Later pages cover each EXPLAIN table and each access-path type in depth.
The SQL EXPLAIN statement captures access-path information for an explainable statement and inserts rows into EXPLAIN tables. A statement is explainable if it is SELECT, MERGE, TRUNCATE, or INSERT, or the searched form of UPDATE or DELETE. PLAN_TABLE describes each step. If DSN_STATEMNT_TABLE exists, Db2 adds an estimated cost. If DSN_FUNCTION_TABLE exists, it records how user-defined functions were resolved. Other DSN_* tables fill when present.
EXPLAIN does not include referential-constraint enforcement steps. For system-period temporal tables, output can show both the base table and the history table when both are needed. SQL against a declared temporary table must be explained in the same application process that declared the table; a static EXPLAIN of that SQL is incrementally bound at run time, not at BIND.
PLAN_TABLE must exist before EXPLAIN or BIND EXPLAIN(YES) can succeed. It must be a base table, optionally with aliases named PLAN_TABLE for other authids. It cannot be a view or a synonym. The owner (or alias owner) needs SELECT and INSERT.
Only PLAN_TABLE is required for basic EXPLAIN. Create the others when you need predicate, filter, sort, or detailed-cost data. After migration, upgrade old-format tables; Db2 12+ issues SQLCODE -20520 (and may skip EXPLAIN) if the tables are still at a retired format.
| Method | Static or dynamic | New access path? |
|---|---|---|
| EXPLAIN PLAN FOR sql | Either (dynamic PREPARE of the whole EXPLAIN) | Yes — optimizer runs |
| BIND/REBIND EXPLAIN(YES) | Static statements in the package/plan | Yes — and the package is bound |
| BIND/REBIND EXPLAIN(ONLY) | Static statements | Yes — package is not replaced |
| EXPLAIN PACKAGE | Static (already bound) | No — extract from the package |
| CURRENT EXPLAIN MODE | Dynamic in the session | Yes — at prepare of each statement |
| EXPLAIN STMTCACHE | Dynamic already in the cache | No — extract from the cache |
12345EXPLAIN PLAN SET QUERYNO = 1001 FOR SELECT EMPNO, LASTNAME FROM HR.EMPLOYEE WHERE WORKDEPT = 'A00';
PLAN and ALL have the same effect. SET QUERYNO = integer tags every inserted row so you can find them later. If you omit QUERYNO, an embedded EXPLAIN uses the precompiler statement number; a dynamic EXPLAIN gets a Db2-assigned number. The FOR clause must be statement text—not a statement-name or host-variable. To explain dynamic SQL, PREPARE the entire EXPLAIN statement. The explained SQL must not contain its own QUERYNO clause. Host variables are allowed in embedded EXPLAIN; parameter markers are allowed when EXPLAIN is dynamically prepared.
Authorization: the privileges of the explained statement, plus PLAN_TABLE ownership or alias with SELECT and INSERT. EXPLAIN privilege, SQLADM, or system DBADM can also satisfy PLAN/ALL. STMTCACHE and PACKAGE forms need SQLADM, SYSADM, or related authorities (STMTCACHE ALL is SQLADM / system DBADM / SYSADM; without that, you only see statements with your own authid).
Extracts paths that are already in the dynamic statement cache on this data-sharing member. No new optimization. Variants:
Copies existing static paths from a bound package into the current user’s PLAN_TABLE. Other EXPLAIN tables are not populated. Scope by COLLECTION, PACKAGE, optional VERSION, and COPY CURRENT / PREVIOUS / ORIGINAL (PLANMGMT copies). HINT_USED is set to EXPLAIN PACKAGE: copy-id. This is how you document what production is running without rebinding.
From function level 500, extracts cataloged stabilized dynamic SQL into PLAN_TABLE, DSN_STATEMNT_TABLE, and DSN_FUNCTION_TABLE by STMTID and COPY CURRENT or INVALID. QUERYNO is 0; COLLID is DSNSTBLQRYEXPLAIN.
The usual capture is the bind option:
12REBIND PACKAGE(PAYROLL.PAYUPD.(VPROD)) - EXPLAIN(YES)
EXPLAIN(YES) binds (or rebinds) and inserts rows for every explainable statement in that package or plan. It does not explain SQL inside packages that a plan merely lists. EXPLAIN(ONLY) runs the same optimizer work and locking but does not replace the directory package—use it to trial APREUSE. owner.PLAN_TABLE must exist; VALIDATE(BIND) fails the bind if it does not. QUERYNO is the DBRM statement number. You can also embed SQL EXPLAIN inside the program even if the package was bound EXPLAIN(NO).
Three patterns:
123SET CURRENT EXPLAIN MODE = EXPLAIN; -- next PREPARE/EXECUTE or JDBC execute is explained, not run SET CURRENT EXPLAIN MODE = NO;
Start with PLAN_TABLE. One statement often produces several rows: one per query block (QBLOCKNO) and per step (PLANNO) inside that block (each table in a join, each sort).
1234567SELECT QUERYNO, QBLOCKNO, PLANNO, TNAME, ACCESSTYPE, ACCESSNAME, MATCHCOLS, INDEXONLY, METHOD, PREFETCH, SORTN_UNIQ, SORTC_GROUPBY, JOIN_TYPE, HINT_USED, BIND_EXPLAIN_ONLY FROM PLAN_TABLE WHERE QUERYNO = 1001 ORDER BY QBLOCKNO, PLANNO;
Column cheat-sheet for a first read:
| ACCESSTYPE | Meaning |
|---|---|
| R | Tablespace / table scan |
| I | Matching index scan (MATCHCOLS > 0 typically) |
| I1 | One-fetch index access |
| N | Index access with IN-list |
| MX / DI / DU / M / NR / V / P / RW / H | Multi-index, direct, and other special methods — see the access-path pages |
Join DSN_STATEMNT_TABLE on QUERYNO (and APPLNAME/PROGNAME when present) for estimated milliseconds and service units. That cost is the optimizer’s model, not a promise of wall-clock time. DSN_PREDICAT_TABLE lists each predicate and whether it is stage 1 or index matching. DSN_DETCOST_TABLE and DSN_FILTER_TABLE add more detail once you know which step is the problem.
Bind EXPLAIN rows for a package also carry COLLID, PROGNAME, VERSION. Always filter on those plus QUERYNO and a recent EXPLAIN_TIME; PLAN_TABLE accumulates history unless you delete old rows.
EXPLAIN is asking the GPS for the route without driving the car. PLAN_TABLE is the turn-by-turn list: which street (table), whether you used a shortcut (index), and whether you waited at a merge (sort or join). BIND EXPLAIN(YES) writes the route into the notebook when you pack the suitcase (the package). EXPLAIN(ONLY) draws the route on scrap paper without repacking. CURRENT EXPLAIN MODE is a sticker on your backpack that copies every new sticky-note recipe into the notebook. STMTCACHE is photocopying the route that cars in the parking lot already used, without asking GPS again.
1. Which EXPLAIN table is required for basic EXPLAIN to work?
2. How do you EXPLAIN static SQL in a package without replacing the package?
3. What does SET CURRENT EXPLAIN MODE = EXPLAIN do?
4. What does ACCESSTYPE = I usually mean in PLAN_TABLE?
5. Does EXPLAIN STMTCACHE pick a new access path?