Oracle里用EXPLAIN PLAN看看SQL执行计划,帮你理解语句到底咋跑的分析示范
- 问答
- 2026-01-06 02:13:13
- 22
参考了Oracle官方文档中关于SQL调优和EXPLAIN PLAN的章节,以及多位资深DBA如Thomas Kyte在其著作和论坛讨论中强调的实践方法)
想象一下,你让一个朋友去一个陌生的城市帮你办几件事,比如先去银行取钱,再去邮局寄包裹,最后去超市买东西,他回来之后,你肯定会问他:“你是怎么走的?先去了哪里?路上顺利吗?有没有堵车?” 你问的这个问题,就有点像数据库里的“执行计划”,SQL语句就是你给数据库下达的命令,而执行计划就是数据库这个“朋友”告诉你,它打算如何一步一步地去完成这个任务。
在Oracle数据库里,EXPLAIN PLAN 就是这个让你能提前知道数据库“行动路线”的工具,你不用真的让SQL语句跑完(尤其是那些可能要跑几个小时的大语句),就能先看看数据库打算怎么执行它,从而判断这个方法是不是高效,有没有走冤枉路。
下面我们就来一步步看看怎么用这个工具,以及怎么看懂它输出的“路线图”。
第一步:创建一张“计划表”
我们需要一张桌子来记录数据库告诉我们的计划,Oracle通常已经为你准备好了一张名为 PLAN_TABLE 的表,你可以检查一下你的数据库用户下有没有这张表,如果没有,也很简单,Oracle提供了一个现成的脚本来创建它,这个脚本通常叫 utlxplan.sql,位于Oracle软件的安装目录下,你只需要以有权限的用户登录数据库,然后执行这个脚本就行了,绝大多数情况下,这张表是已经存在的。
第二步:让数据库生成计划

我们有一条想分析的SQL语句,
SELECT e.employee_name, d.department_name FROM employees e, departments d WHERE e.department_id = d.department_id AND e.salary > 5000;
我们要让数据库为这条语句生成执行计划,并把它写入到 PLAN_TABLE 中,命令非常简单:
EXPLAIN PLAN FOR SELECT e.employee_name, d.department_name FROM employees e, departments d WHERE e.department_id = d.department_id AND e.salary > 5000;
执行这条命令后,数据库并没有真正去查询数据,它只是静静地分析了一下语句,然后把自己的“作战方案”详细地记录在了 PLAN_TABLE 表里。
第三步:查看“路线图”
计划已经生成了,但它存储在表里,是一堆原始数据,可读性很差,这时候我们需要一个“翻译官”来把计划表里的内容以一种容易理解的方式展示给我们看,最常用的工具就是 Oracle 自带的 DBMS_XPLAN 包。
你只需要执行:
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

这条命令会从 PLAN_TABLE 中读出最近一次生成的执行计划,并以一个清晰的树形结构显示出来,我们来重点学习怎么看懂这个输出结果。
解读执行计划的核心要点
执行计划的输出通常有好几列,我们盯住几个最关键的就够了:
-
Id & Operation (操作 ID 和操作类型):这是执行计划的骨架,告诉你数据库要执行哪些操作。
TABLE ACCESS FULL(全表扫描)、INDEX RANGE SCAN(索引范围扫描)、NESTED LOOPS(嵌套循环连接)、HASH JOIN(哈希连接)等,这些名字非常直观,FULL就是读取整个表,INDEX SCAN就是去查索引。 -
Rows (基数估计):这一列是数据库预估的每一步操作会返回多少行数据,这个数字非常重要!因为数据库优化器就像是公司的调度员,它根据这个预估的行数来决定走哪条路最快,如果这个预估值和实际情况差得太远(比如它以为只有10行,结果实际有100万行),那它很可能就制定了一个非常糟糕的计划,看到执行计划,第一眼就可以看看这个
Rows的预估是否合理。 -
Cost (成本):这是一个相对的、无量纲的数字,代表了数据库认为执行这一步操作的“代价”有多大,代价考虑了CPU、磁盘I/O等因素,我们会希望整个计划的总代价越小越好,但要注意,
Cost是用来比较不同计划孰优孰劣的,它本身绝对值的大小没有直接意义。
-
Access Predicates 和 Filter Predicates (访问条件和过滤条件):这能帮你理解数据是如何被筛选的。
Access Predicates是那些用于快速定位数据的原则(比如通过索引直接找到某几行),而Filter Predicates是等数据拿出来之后再进行的过滤。
让我们模拟一个简单的计划输出分析:
假设上面那条连接查询的计划输出可能是这样的:
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 100 | 3900 | 6 (17)| 00:00:01 |
| 1 | NESTED LOOPS | | 100 | 3900 | 6 (17)| 00:00:01 |
| 2 | TABLE ACCESS FULL| EMPLOYEES | 10 | 190 | 3 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| DEPARTMENTS | 10 | 200 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
怎么读这个计划?
- 执行顺序是从内层(Id大的)向外层(Id小的)看,如果缩进相同,则从上往下看,所以数据库的实际执行步骤是:Id 2 -> Id 3 -> Id 1 -> Id 0。
- Id 2: 它对
EMPLOYEES表进行了一次全表扫描 (TABLE ACCESS FULL),预估会返回10行满足salary > 5000条件的数据。 - Id 3: 对于从Id 2步骤中获取的每一行员工数据,数据库都去
DEPARTMENTS表里做一次全表扫描,目的是为了找到与员工部门ID相匹配的部门名称,注意看下面的Predicate Information,这里说明了连接条件e.department_id = d.department_id是在这一步作为过滤条件应用的。 - Id 1: 这是
NESTED LOOPS(嵌套循环)连接操作,你可以把它想象成一个双循环:对于第一个表(员工表)的每一行,都去第二个表(部门表)里循环一遍找匹配的行,这种连接方式在驱动集(员工表)很小的时候效率很高。 - Id 0:
SELECT STATEMENT表示最终的结果集被返回给用户。
从这个计划我们能学到什么?
这个计划看起来简单,但已经能给我们一些启示。EMPLOYEES 表非常大,但高薪员工(salary > 5000)确实只有几十个,那么这个计划是高效的,因为外层循环次数很少,如果 DEPARTMENTS 表很大,对它的每次全表扫描代价就会很高,这时,我们可能会想:能不能在 DEPARTMENTS 表的 department_id 上建个索引?这样,第二步就可以从全表扫描变成高效的索引扫描了。
通过这个简单的例子,你应该能体会到 EXPLAIN PLAN 的强大之处,它就像给你的SQL语句做了一次“X光检查”,让你能洞察数据库内部的执行逻辑,从而为优化SQL性能提供最直接的依据,核心就是看懂它的“行动路线”(Operation)、评估它的“工作量预估”(Rows)、并思考是否有更优的路径(比如使用索引)。
本文由芮以莲于2026-01-06发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://www.haoid.cn/wenda/75299.html
