一、Oracle SQL 解析
在 Oracle 处理用户提交的 SQL 前,必须完成一个重要的步骤:SQL 解析。Oracle SQL 解析的过程如图所示:

- Oracle SQL 解析主要分为软解析、硬解析、以及软软解析;
- 硬解析需要消耗较多的资源,应当尽量减少硬解析;
- 软解析是性能较高的解析方式,从 Library Cache 缓存中获取 SQL 游标(可能需要 Reload);
- 软软解析是一种特殊的解析方式(不解析),Oracle 将直接从 Session 游标缓存中读取 SQL 游标,是性能最高的解析方式。
1、硬解析
在 SQL 语句第一次执行前,系统需要对该 SQL 进行硬解析操作。Oracle 硬解析主要包括以下规则:
- SQL 语法检查(检验 SQL 语法的正确性),例如:
// 语法检查
SQL> select *;
select *
ORA-00923: FROM keyword not found where expected可以看到,在 SQL执行前,需要进行 SQL 语法的检查。
- SQL 语义检查(表、列以及关联对象等),通过查询数据字典,确认 SQL 引用的对象是否存在,如下:
SQL> desc emp;
Name Type Nullable Default Comments
-------- ----------------- -------- ------- --------
EMPNO NUMBER(4)
ENAME VARCHAR2(10 BYTE) Y
JOB VARCHAR2(9 BYTE) Y
MGR NUMBER(4) Y
HIREDATE DATE Y
SAL NUMBER(7,2) Y
COMM NUMBER(7,2) Y
DEPTNO NUMBER(2) Y
SQL> select emp.empno, emp.loc from emp;
select emp.empno, emp.loc from emp
ORA-00904: "EMP"."LOC": invalid identifier可以看到,在 SQL 执行前,需要进行 SQL 语义的检查。
- SQL 游标的用户访问权限检查,用于验证解析用户是否对 SQL 游标引用的对象拥有足够访问权限:如果有足够权限,则放行解析;否则退出解析环节。
- 解析锁定,保证解析树完整性;
- 优化器选择,系统评估选择优化器(CBO/RBO);
- 产生与优化器匹配的执行计划。
2、软解析
在 SQL 硬解析之前,Oracle 会对用户当前提交的 SQL 进行 Hash 运算,然后将计算得到的 Hash 值与 Library Cache 中 SQL 游标缓存的 Hash 值进行比对。如果能够在 Library Cache 找到与用户提交 SQL 匹配的 SQL 游标缓存(Hash值相同),那么 Oracle 就会重用先前缓存的 SQL 游标而不进行硬解析操作,这个过程就是软解析。
提示:在10g以前,判断 SQL 与 SQL 游标缓存是否匹配是通过 sql_hash_value 来决定的。实际上,在10g以后使用 sql_id 就可以唯一判断了。
Oracle 软解析主要包含如下规则:
- SQL 游标的私有 SQL Area 在 Session PGA 中创建。
- SQL 游标的共享 SQL Area 存放在 Library Cache 中:
□ 如果新用户发起的 SQL 能够找到相匹配的缓存 SQL 游标,则软解析;
□ 如果新用户发起的 SQL 不能找到相匹配的缓存 SQL 游标,则硬解析。
- 大多数应用解析一次,执行多次。
- 软解析(硬解析)次数应当尽可能少。
Oracle 软解析前置步骤:在 Oracle 软解析实现前,必须进行用户名称转换和授权验证这2个步骤。
1)用户名称转换:当 Session 用户第一次使用存放在 Library Cache 中的共享游标前,首先需要对该共享游标进行用户名称转换(游标解析主体为其他用户),转换之后才能结合授权验证来重用共享游标进行软解析。
2)授权验证:当用户名称转换和授权验证通过后,还仍然需要对授权进行验证。这是因为如果游标的先前用户最后一次执行该游标后,游标相关对象的授权发送了变化,那么就需要再次对授权进行验证才能实现软解析。
3、软软解析
软软解析也称为不解析,与参数 session_cached_cursors 紧密相关。从某种意义上讲,软软解析也可以看作特殊的软解析。
一旦使用参数 session_cached_cursors 后,Session 用户就可以直接重用缓存在 Session 游标缓存区的 SQL 游标。与此同时,只要 SQL 游标保留在 Session 游标缓存区内,任何关闭 SQL 游标的行为都将被忽略,这样就提高了 SQL 游标的完全重用率,大大减少了 SQL 解析次数,对数据库性能提升产生了深远的影响。
二、Oracle SQL 执行
当 SQL 解析完成后,系统需要将 SQL 提交后台执行,而 SQL 执行最重要的依据就是 SQL 执行计划。
1、SQL 执行计划(树)概览
Oracle 执行计划输出是一棵完整的执行计划树,执行计划(树)的特点有:
- 执行计划中的最高节点是执行计划的起点;
- 执行计划中的每一行代表一行数据源;
- 当2个子 Level 处于同级 Level 时,子 Level 值大的优先执行;
- 执行计划体现了 SQL 执行的逻辑层次和先后顺序。
2、SQL 执行计划(树)与父子 Level
执行计划(树)中包含了父 Level 和子 Level,用于展示执行计划中执行步骤的先后执行顺序。执行计划于父子 Level 关系是:从父 Level(Level0)开始执行,找到所有匹配的分支 Level 递归执行,直到无分支 Level 时结束,如下所示:
SQL> explain plan for
2 select e.mgr, d.dname, b.sal
3 from emp e, dept d, bonus b
4 where e.deptno=d.deptno
5 and e.ename=b.ename;
Explained
SQL> set lin 100;
Cannot SET LIN
SQL> set pagesize 100;
SQL> select id ||' '|| parent_id ||' '|| lpad(' ',2*level-1) || operation || ' ' || options || ' ' || object_name || ' (Cost=' || cost || ')' as "Execute Plan"
2 from plan_table
3 start with id=0
4 connect by prior id=parent_id;
Execute Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT (Cost=7)
1 0 NESTED LOOPS (Cost=)
2 1 NESTED LOOPS (Cost=7)
3 2 HASH JOIN (Cost=6)
4 3 TABLE ACCESS FULL BONUS (Cost=2)
5 3 TABLE ACCESS FULL EMP (Cost=3)
6 2 INDEX UNIQUE SCAN PK_DEPT (Cost=0)
7 1 TABLE ACCESS BY INDEX ROWID DEPT (Cost=1)
8 rows selected通过上述内容可以看到:
1)执行计划从父 Level 0 开始,父 Level 只有一个分支 Level 1,如下所示:
Execute Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT (Cost=7)
1 0 NESTED LOOPS (Cost=) -->pid=0,level 1可以看到,Level 0 只包含 Level 1 这样的 1 个分支 Level。
2)子 Level 1 开始递归,子 Level 1 有 2 个分支 Level 2 和 Level 7,如下:
Execute Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT (Cost=7)
1 0 NESTED LOOPS (Cost=)
2 1 NESTED LOOPS (Cost=7) -->pid=1,level 2
......
7 1 TABLE ACCESS BY INDEX ROWID DEPT (Cost=1) -->pid=1,level 7可以看到,Level 1 包含 Level 2 和 Level 7 这样的 2 个分支 Level,由于 Level 7 优先级高于 Level 2(pid7 > pid2),因此,Level 7 优先执行。
3)对于优先执行的 Level 7 ,发现没有子 Level ,结束分支;
4)对于与 Level 7 平级的 Level 2 分支,递归遍历发现存在 2 个分支:Level 3 和 Level 6,如下所示:
Execute Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT (Cost=7)
1 0 NESTED LOOPS (Cost=)
......
3 2 HASH JOIN (Cost=6) -->pid=2,level 3
......
6 2 INDEX UNIQUE SCAN PK_DEPT (Cost=0) -->pid=2,level 6
......可以看到,Level 2 包含 Level 3 和 Level 6 这样的 2 个分支 Level,由于 Level 6 优先级高于 Level 3(pid6 > pid3),因此,Level 6 优先执行。
5)对于优先执行的 Level 6 的分支,发现没有子 Level,结束分支;
6)对于与 Level 6 平级的 Level 3 分支,递归遍历发现存在 2 个分支:Level 4 和 Level 5,如下所示:
Execute Plan
--------------------------------------------------------------------------------
0 SELECT STATEMENT (Cost=7)
1 0 NESTED LOOPS (Cost=)
2 1 NESTED LOOPS (Cost=7)
......
4 3 TABLE ACCESS FULL BONUS (Cost=2) -->pid=3,level 4
5 3 TABLE ACCESS FULL EMP (Cost=3) -->pid=3,level 5可以看到,Level 3 包含 Level 4 和 Level 5 这样的 2 个分支 Level,由于 Level 5 优先级高于 Level 4(pid5 > pid4),因此,Level 5 优先执行。
7)对于优先执行的 Level 5 的分支,发现没有子 Level,结束分支;
8)对于与 Level 5 平级的 Level 4 分支,发现没有子 Level,结束分支。
至此,实现了执行计划的执行过程。
我们将执行计划父子 Level 的执行过程概括为图 5-3 所示。
其中:
□ “A.B” 格式代表 “父 Level.子Level”,例如1.2 表示 父 Level 1 和 子 Level 2 的执行步骤,如下所示:
2 1 NESTED LOOPS (Cost=7) -->pid=1,level 2□ “<<>>” 表示 Level 分支。
不难看出,Oracle 执行计划展示了 SQL 执行的逻辑层次与先后顺序。

技巧:通过执行计划(树)可以看到,Oracle 连接查询最为常见的是两张表的连接查询。换句话说,任意复杂的连接查询都可以简化为基本的两表连接。




