2、SQL执行原理
2.1、软解析与硬解析
在执行每一条sql的时候,oracle必须解析sql并且装载到高速缓存中,解析的过程包括很多内容,验证语法、检验提及的对象、确认用户权限等过程后还需要手机它所需要的所有信息,需要很多次的系统对象查询,所以说解析sql与不解析sql之间差别数十次的系统对象查询。
软解析:一条sql在验证完并验证通过一些基础的信息后去高速缓存中看这条语句是否执行过,如果执行过就取回之前解析的信息并重用。
硬解析:如果没有执行过,oracle将解析这条sql,生成执行计划,并将它放入高速缓存以便将来重用。
在高速缓存中存放的是解析完的sql的散列值,所以说每一次修改sql对oracle来说都是一个不同的sql,包括加注释,查看告诉缓存中的sql可以使用v$sql视图来查看(这里有一个技巧,为了方便查找我们可以给我们的sql定义一个唯一的id(注释)方便以后查询)。
SELECT/* key-menu */*fromho_menuwherename like'我的控制台';
select*fromv$sqlwheresql_text like'%key-menu%';
2.2、查询转换
块是oracle进行操作的最小单位,下面是只有一个查询块
select*fromemployeeswheredepartment_id=1
而下面就是有两个查询块
select*fromemployeeswheredepartment_idin(selectdepartment_idfromdepartments)
查询块是通过select关键字来定义的,查询转换就是查询块的转换,在sql通过了语法与权限检查后就会进行查询转换,就是确定如果改变了查询写法会不会提供更好的查询计划,所以查询转换器可能会改变你所写的sql查询结构,但是不会影响最终的查询结果集。
最通常的转换就是将独立的查询快转换为直接连接:
select*fromemployeeswheredepartment_idin(selectdepartment_idfromdepartments)
转换后:
select*fromemployees e,departments dwheree.department_id=d.department_id
你可以通过查看执行计划来了解是否发生了查询转换。
常用的几种转换能够引用到特定的查询中:
视图合并
子查询解嵌套
谓语前推
使用物化试图查询重写 值得注意的是使用rownum在决定执行计划的时候会影响优化器进行选择,所以一定要小心使用,它将阻止试图合并也不能推进谓词。
3、SQL的执行计划
首先要知道的是解释计划与执行计划:
解释计划就是说当SQL语句执行的时候应该采用的预期执行计划,是一个估计值而非实际值。
执行计划就是说得到的实际执行计划以及与其相关的数据源执行统计信息。 解释计划与语句的实际执行时所使用的计划可能不一致(日了狗了);
解释计划可以通过oracle下的开发工具SQL Developer来生成,也可以使用其它方式生成。
3.1、解释计划
3.1.1、生成解释计划
使用EXPLAIN PLAN FOR(也可以使用sqlplus的autotrance命令来执行,相关知识自查)来为一个查询生成预期的执行计划,也就是解释计划。
语法:
explain planforsql;--生成信息并存到表plan_table中;
select*fromtable(dbms_xplan.display);
--dbms_xplan.display函数非常友好的基于每一条sql产生的计划自动显示适当的plan_table中的列。你可以自行查看plan_table中有那些列信息;
例子:
先执行:
explain planforselect*fromHO_USER;
生成计划,然后执行下面的语句查询计划:
select*fromtable(dbms_xplan.display);
输出为(基本格式就是这样,稍微修正):
Plan hash value: 3700583471
Id
Operation
Name
Rows
Bytes
Cost (%CPU)
Time
0
SELECT STATEMENT
3
315
3 (0)
00:00:01
1
TABLE ACCESS FULL
HO_USER
3
315
3 (0)
00:00:01
使用explain plan for来为一个查询生成预期的执行计划时输出包括下面几种:
sql语句中所使用的每一张表;
访问每张表所使用的方法;
每一对需要链接的数据源所用的连接方法(join);
按次序列出的所有需要完成的运算;
计划中各步骤的谓语信息列表;
对于每一个运算,估算出该步骤所要操作的数据行数和字节数(字节数是指结果集中包含的总字节数,行数乘以每一行的字节数,每行的字节数是将所选择列表中包含的没一列的平均长度相加来确定);
对于每一个运算计算出成本值;
如果适用,所访问的分区信息;
如果适用,并行执行的相关信息;
下面列出一个稍微复杂的查询过程:
explain planforselectt.DEVICE_NAME,t.dep_code,d.NAMEfromJC_DEVICE_COLLECT t,HO_DEPARTMENT dwheret.DEP_CODE=d.CODE;.
select*fromtable(dbms_xplan.display);
结果:
Planhash value:3282717052
Id
Operation
Name
Rows
Bytes
Cost (%CPU)
Time
0
SELECT STATEMENT
20
1020
6 (17)
00:00:01
1
MERGE JOIN
20
1020
6 (17)
00:00:01
2
TABLE ACCESS BY INDEX ROWID
HO_DEPARTMENT
8
200
2 (0)
00:00:01
3
INDEX FULL SCAN
IDX_HO_DEPARTMENT_CODE
8
1 (0)
00:00:01
4
SORT JOIN
20
520
4 (25)
00:00:01
5
TABLE ACCESS FULL
JC_DEVICE_COLLECT
20
520
3 (0)
00:00:01
PredicateInformation(identifiedbyoperation id):
---------------------------------------------------
4-access("T"."DEP_CODE"="D"."CODE")
filter("T"."DEP_CODE"="D"."CODE")
3.1.2、阅读计划
计划是按照顺序ID号来显示的,但是并不是严格按照自上而下来执行的,缩进最多的是执行过程中首先去执行的运算,同一层上的多个运算是自上而下依次执行,使用sqldeveloper能够很容易查看,并且直观显示出父子关系,子处理完交给父;
计划中最有用的部分之一就是谓语信息的部分,这个部分将会出现access predicates(访问谓语运算)、filter predicates(筛选谓语运算)
3.2、执行计划
3.2.1、查看执行计划
当sql语句执行的时候就会生成实际的执行计划,在sql被硬解析后所选的执行计划就会被存到库高速缓存备用,可以通过v$sql_plan来查看计划运算。
v$sql_plan与plan_table基本相同,知识添加了一些易于定位的列,比如sql_id、hash_value等,我们可以通过如下的方式查询执行计划:
selectsql_id,sql_textfromv$sqlwheresql_text like'%ho_menu%';--得到sql_id
select*fromv$sql_planwheresql_id='ds8jqcuyaf6gu';--查询这条sql相关的计划
还有其它方式定位执行计划,我比较喜欢这种方式,还有更直观的是使用dbms_xplan.display_cursor(sql_id,child_number,format)函数;
selectsql_id,sql_textfromv$sqlwheresql_text like'%ho_menu%';--得到sql_id
select*fromtable(DBMS_XPLAN.DISPLAY_CURSOR('ds8jqcuyaf6gu',null));--没有值的使用默认值
这样查出来与解释计划格式类似,可以对比执行计划与解释计划是否一致。