EXPLAIN PLAN FOR 必须用 executeUpdate() 执行,因其属DDL类语句;需设唯一STATEMENT_ID并严格过滤查询;推荐用DBMS_XPLAN.DISPLAY查计划并手动清理PLAN_TABLE。
EXPLAIN PLAN FOR 语句必须用 executeUpdate() 而不是 executeQuery()
很多人一上来就对 explain plan for select ... 调用 executequery(),结果抛出 sqlexception: ora-00900: invalid sql statement。这是因为 explain plan for 是 ddl 类语句(虽不改数据,但修改 plan_table),jdbc 要求用更新类方法执行。
实操建议:
- 用
PreparedStatement.executeUpdate()执行EXPLAIN PLAN FOR ...,返回值恒为 0,不用关心 - 确保目标用户有
INSERT权限到PLAN_TABLE(默认是全局临时表,但权限仍需显式授予) - 别在同一个
Connection中连续执行多个EXPLAIN PLAN FOR—— 默认不自动清空PLAN_TABLE,会混入旧计划
读取 PLAN_TABLE 必须加 STATEMENT_ID 隔离不同查询
Oracle 的 PLAN_TABLE 是共享的,没有自动隔离机制。如果并发或多次调用没设唯一标识,SELECT * FROM PLAN_TABLE 会捞出所有历史计划,根本分不清哪条属于当前 SQL。
实操建议:
- 执行前先生成唯一
STATEMENT_ID,比如用UUID.randomUUID().toString().substring(0, 30)(Oracle 限制 30 字符) EXPLAIN PLAN SET STATEMENT_ID = 'xxx' FOR SELECT ...- 查计划时严格按
WHERE STATEMENT_ID = 'xxx'过滤,再用DBMS_XPLAN.DISPLAY或直接查表 - 避免用
EXPLAIN PLAN FOR+SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY())这种嵌套方式 —— JDBC 不支持多结果集(DBMS_XPLAN.DISPLAY返回的是 pipelined table,驱动可能报ORA-22905)
用 DBMS_XPLAN.DISPLAY 比手拼 PLAN_TABLE 字段更可靠
直接查 PLAN_TABLE 字段(如 OPERATION, OPTIONS, OBJECT_NAME)看似简单,但字段含义依赖 Oracle 版本(比如 COST 在 12c+ 含义变化)、缩进逻辑要自己递归处理、PARENT_ID 在旧版不全,极易漏掉 filter、access pred 等关键信息。
立即学习“Java免费学习笔记(深入)”;
实操建议:
- 用
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY('PLAN_TABLE', 'xxx', 'BASIC +PREDICATE +COST'))查计划 - 注意第三个参数是格式选项字符串,不是数组;
'BASIC'是最小可用集,'+PREDICATE'和'+COST'必须带加号 - JDBC 中用
executeQuery()执行该 SELECT,结果集每行是计划的一行文本(PLAN_LINE列),直接逐行读取即可 - 如果用 Oracle 12c+,可考虑
DBMS_XPLAN.DISPLAY_CURSOR(需已执行真实 SQL 并保留游标),但EXPLAIN PLAN FOR场景下不适用
事务和连接生命周期要手动清理 PLAN_TABLE
PLAN_TABLE 默认是全局临时表(ON COMMIT DELETE ROWS),但很多 DBA 会改成 ON COMMIT PRESERVE ROWS 或换成普通表 —— 这时不清理就会越积越多,甚至影响性能。
实操建议:
- 每次读完计划后,立即执行
DELETE FROM PLAN_TABLE WHERE STATEMENT_ID = 'xxx' - 把
EXPLAIN PLAN、查计划、清理三步包进一个 try-with-resources 或 finally 块,防止异常跳过清理 - 不要依赖连接关闭自动清理:即使临时表,连接池复用也会让
PLAN_TABLE数据残留 - 如果应用跑在多租户环境,确认
PLAN_TABLE是私有同义词(指向用户自己的PLAN_TABLE),而非公共同义词(指向SYSTEM.PLAN_TABLE)
最麻烦的点其实是 STATEMENT_ID 的生成和传递一致性 —— 多线程里用时间戳或自增 ID 容易冲突,用 UUID 最省心,但得截断到 30 字符,否则 Oracle 直接报错。

















