【云和恩墨,提供7*24最专业的数据恢复(Oracle,MySQL,SQL server)服务,致力于为您的数据库系统作最后一道安全防御!服务热线:010-59007017-7030】数据恢复|数据库运维|性能优化|安全保障|Oracle培训|MySQL培训html
主题介绍:程序员
Oracle执行计划的另类解读:调皮的执行计划 | 诚实的执行计划 | 朴实的执行计划web
说到执行计划,oracle的拥趸们天然而然会兴奋起来。在ORACLE的世界里,执行计划有着其特殊的地位,若是咱们将SQL性能优化当作一个生物,那某种程度上,执行计划就是DNA。在某搜索网站中,“oracle 执行计划”关键字的搜索结果与“oracle”关键字的搜索结果占比为1.7%。足可见执行计划在ORACLE中举足轻重的地位:数据库
而当咱们输入“oracle执行计划”时,推荐关键字排第一的就是“ORACLE执行计划怎么看”安全
一个标准的执行计划大体能够分为三个部分:访问方式(表访问、索引访问等)、链接方式(NESTED LOOP、HASH JOIN等)及访问顺序(驱动表等)性能优化
咱们对上述SQL稍加改动,再看执行计划:oracle
什么状况?DEPT表不见了,执行计划竟然“残缺”了:运维
一、这是ORACLE的BUG吗?函数
二、少了一张表,结果正确吗?工具
三、ORACLE优化器如此大胆,其背后是谁在给他撑腰?
四、ORACLE凭什么擅做主张?
为了回答上述问题,咱们就进入今天的第一个主题:残缺的执行计划。
残缺的执行计划
在展开以前,咱们先作数据准备,分别建立两张表EMP及DEPT,脚本以下:
CREATE TABLE DEPT(
DEPTNO NUMBER(2),
DNAME VARCHAR2(14),
LOC VARCHAR2(13));
CREATE TABLE EMP(
EMPNO NUMBER(4)CONSTRAINT PK_EMP PRIMARYKEY,
ENAME VARCHAR2(10),
JOB VARCHAR2(9),
MGR NUMBER(4),
HIREDATE DATE,
SAL NUMBER(7,2),
COMM NUMBER(7,2),
DEPTNO NUMBER(2) );
如今咱们有以下一条语句:
SELECT COUNT(1)
FROM EMP E
LEFTJOIN DEPT D
ON E.DEPTNO = D.DEPTNO
这条语句很是简单,就是获取EMP表与DEPT表内关联后的数据量。在看具体的执行计划以前,咱们解读下在常规状况下,DB是如何处理这样的数据的
一、分别读取emp表和DEPT表的数据;
二、对EMP中的DEPTNO与DEPT表中的DEPTNO进行内关联;
三、对内关联后的数据进行汇总计算;
四、返回汇总计算结果。
也就是说会存在EMP与DEPT表的内关联,由于SQL就是这样写的。那咱们看下该语句的执行计划,以下:
ORACLE优化器果然是按照咱们的预想制定了执行计划。
1惟一性字段对执行计划的影响
因为在模型分析时,咱们发现DEPT表的DEPTNO字段是惟一的。因而咱们须要经过以下语句为该字段建立主键:
ALTERTABLE DEPT ADDCONSTRAINT PK_DEPT PRIMARYKEY(DEPTNO);
咱们再回过头来看执行计划,会发生变化吗?
若是此时的你还不能看出问题,那么咱们就对比下DEPT表的主键建立前后执行计划的变化:
俗话说:不比不知道,一比吓一跳。DEPT莫名其妙的被ORACLE优化器弄“丢”了。这不由让人怀疑:这样的裁剪是不是不负责任的?也就是说,裁剪后的结果是否会由于裁剪而发生变化?在深刻了解到LEFT JOIN的原理及模型结构后,你就会明白为什么ORACLE优化器在DEPT表建立了基于DEPTNO字段的主键后,会作这样的裁剪。
支持ORACLE作如此大胆裁剪的理由是:
一、 LEFTJOIN在没有where条件过滤的时候,是不会减小结果数据量的;
二、 若是被关联的字段是被关联表的主键(或者惟一性字段),那么是不会使结果数据量增多的。
既然结果集的数据量不增长也不减小,那为什么还要多访问一个表,多作一次关联呢?这就是ORACLE的精明之处:简单的就是高效的。
接下来,咱们继续上面的实验(固然是基于上面的模型基础,即在DEPT表上建立了基于DEPTNO字段的主键)。此次,咱们将LEFT JOIN改为INNER JOIN,看看执行计划是怎么样的:
表结构和约束关系没有发生变化,消失的DEPT又回来了。
神马缘由呢?LEFT JOIN是不会有数据过滤的做用的,可是INNER JOIN则有过滤的功用。
为了验证,咱们准备以下数据:
INSERTINTO dept(deptno,dname)VALUES(14,'财务');
INSERTINTO dept(deptno,dname)VALUES(31,'行政');
INSERTINTO EMP(EMPNO, ENAME, DEPTNO)VALUES('001','张三',14);
INSERTINTO EMP(EMPNO, ENAME, DEPTNO)VALUES('002','李四',31);
INSERTINTO EMP(EMPNO, ENAME, DEPTNO)VALUES('003','王五',21);
INSERTINTO EMP(EMPNO, ENAME, DEPTNO)VALUES('004','麻六',14);
如今来看看LEFT JOIN和INNER JOIN的不一样结果:
也就是说,LEFT JOIN和INNER JOIN仍是有差别的,那么在什么状况下才能在执行计划中将DEPT“枪毙”掉呢?
2主外键约束对执行计划的影响
咱们对EMP和DEPT建立一个主外键约束(在建立主外键约束前,我须要删除掉empno=’003’的记录):
ALTERTABLE EMP ADDCONSTRAINT FK_DEPTNO FOREIGNKEY(DEPTNO)REFERENCES DEPT(DEPTNO);
看看效果如何:
这样是否是已经很是明确了DEPT再度消失的缘由了?由于建立了主外键,也就是等于说EMP全部的DEPTNO必需要存在DEPT表中,既然有这样的约束,那天然就不须要画蛇添足的关联DEPT表了。
3字段属性对执行计划的影响
如今咱们往EMP表里面再添加一条数据:
INSERTINTO EMP (EMPNO, ENAME, DEPTNO)VALUES('005','赵七',NULL);
再看看INNER JOIN的结果:
结果只有3条数据,明显刚才新增的数据是被过滤掉了,由于他的DEPTNO为null,其null并无存在于dept表中。
而在执行计划里面,是没有DEPT表的:
也就是说该SQL就应该等价于以下SQL:
SELECT E.EMPNO, E.ENAME
FROM EMP E
咱们再看结果:
不对呀,说好的等价呢?难道是执行计划出了问题?仍是咱们对执行计划的理解错了?也或许是执行计划对咱们隐藏了什么?
以上,咱们都是在ORACLE的第三方开发工具PL SQL DEVELOPER里面查看的执行计划。如今咱们换种方式,在SQL PLUS里面经过explain plan这种最原始的方式来查看执行计划,以下:
原来,在这个执行计划的内容中,明显的多出了“Predicate Information”,而在这部分信息里面,filter是:E.DEPTNO IS NOT NULL。
好吧,咱们先把这个谓词放进SQL中,看看效果:
果真,咱们发现,增长了这个谓词后,两个SQL又等价了。此时,咱们会想:天哪,若是再遇到其余场景,会不会又不等价了?
在关联条件存在主外键关系约束的前提下,以下两个SQL是等价的:
无论你信不信,反正我信了
而此时,咱们来看看EMP.DEPTNO的字段属性:
咱们发现其Nullable属性为true,便可为null值。而若是咱们将该属性值修改成false呢?
DELETEFROM emp WHERE empno ='005';
COMMIT;
ALTERTABLE EMP MODIFY DEPTNO NOTNULL;
再看执行计划:
咱们发现原来的“Predicate Information”不见了,也就没有了E.DEPTNO IS NOT NULL的谓词约束。
4程序员与ORACLE的较量
在上面,咱们在极力“宣传”着oracle是多么多么的智能化,而事实上,她的智能程度也是存在局限性的,好比她对SQL语句的取舍绝对的依赖于物理模型结构及约束,而一旦这种物理模型结构及约束不存在,那么ORACLE这位“巧妇”显然也只能“难为无米之炊”了。即使咱们在SQL中进行了(惟一性)约束,ORACLE也会选择视而不见,好比以下SQL:
SELECTCOUNT(1)
FROM EMP E
LEFT JOIN (SELECTDISTINCT DEPTNO FROM DEPT) D
ON E.DEPTNO =D.DEPTNO;
按照咱们在上面的理解,因为在子查询D中,已经对DEPTNO进行了distinct处理,也就意味着在子查询D中,DEPTNO绝对是惟一性的,即子查询D对整个SQL返回的结果是没有任何影响的,该SQL彻底等价于以下SQL:
SELECT COUNT(1) FROM EMP E
而事实上呢,咱们看看ORACLE的执行计划:
这一次很让咱们意外,ORACLE竟然没有识别出子查询D的做用。由此看来,在某些时候,尤为是在错综复杂的业务逻辑面前,oracle每每束手无措,远没有程序员聪明,因此在ORACLE这位巨无霸面前,咱们也大可没必要妄自菲薄。
5总结
至此,咱们能够为第一个主题作出以下总结:
一、ORACLE优化器为达性能之目的,会不择手段的简化Operation;
二、ORACLE优化器的手段之一就是充分利用数据库约束,包括但不局限于:惟一性约束、主外键参照性约束、Nullable约束;
三、在约束条件内,ORACLE会简化SQL,在Operation时再也不重复约束;
四、所以,在平常模型设计时,应尽量的创建约束,最大程度上减小重复约束带来的“非战斗性减员”,从而提高SQL性能
完整的执行计划
在上一节的最后示例中,为了更全面阐述问题,咱们“抛弃”了在PL SQL DEVELOPER经过F5获得的执行计划,转而选择了最原始最古老的explain。由于咱们发现:
这几列还不足够支撑咱们了解ORACLE优化器的意图,或者说还不够让咱们拼凑出ORACLE优化器对SQL改写后的全貌。至少咱们还须要谓词(Predicate)。因此,一个完整的执行计划除了:访问方式(表访问、索引访问等)、链接方式(NESTED LOOP、HASH JOIN等)及访问顺序外,还应包括谓词(Predicate),经过结合谓词,咱们更能还原ORACLE优化器对SQL作了哪些改动?
为了直观期间,咱们仍是继续在PL SQL DEVELOPER中演示,只是执行计划的正确打开方式是这样的:
那么咱们能从谓词中发现什么呢?
咱们都知道,在表的统计信息采集及时的场景下,若是某个索引字段存在条件过滤,而执行计划中没有经过索引访问,而是table access full。那么缘由无非就是:该过滤条件值的数据量太大(好比超过全表数据量的20%),或者是SQL的写法不当(该字段上应用了函数、表达式等)。
其实,除了上述两种场景外,还有一种场景也会致使table access full。咱们先来看一个很是简单的案例,咱们在EMP.DEPTNO上建立一个索引,由于常常会遇到查询某个特定部门的员工信息。
CREATEINDEX EMP_I1 ON EMP(DEPTNO);
由于在DEPT表中,DEPTNO的数据类型为NUMBER(2),在查deptno为14的员工信息时,咱们会习惯性的写成:
SELECT*FROM emp WHERE deptno =14;
咱们的如意算盘是经过索引EMP_I1来访问EMP表。而事实上,从执行计划看,倒是table access full的访问方式:
尽管deptno=14的数据量为0,而且也没有在deptno上有任何的函数或者表达式。那么问题出在哪里呢?
我再来看看谓词:
很明显,在实际的执行过程当中,DEPTNO是被TO_NUMBER函数包了一层,天然就走不了索引。那么是什么让ORACLE如此“昏庸”,以至“无事生非”的添加一个函数呢?
咱们再看看EMP.DEPTNO的数据类型:
原来,EMP.DEPTNO的数据类型并无同DEPT.DEPTNO保持一致,被设计成了VARCHAR2。所以要想走索引,就有三种办法:将DEPTNO的数据类型修改成NUMBER(2)、建立TO_NUMBER(DEPTNO)的函数索引、将过滤条件有以前的DEPTNO=14修改为DEPTNO=’14’。
咱们就看下第三种方案的执行计划:
这才是咱们想要的执行计划,却不是咱们想要的表模型。这三种方案孰优孰劣不在本次分享主题范围内,若有机会,再行讨论。
没错,这就是隐式强行转换的风险,而全部的字段隐式转换在执行计划中都会被“曝光”
隐式转换都是“无心为之”,有两种场景:其一是对过滤字段的数据类型“想固然”的认为;其二是对过滤值类型的错误判断。刚才的案例属于第一种,那么第二种又是怎么回事呢?
如下是一个真实的案例:
系统中存在一个日志表,数据量很是大,咱们对日志表按照日志时间(log_date)作了分区。在页面,要求强制按照log_date过滤,以命中分区而提升效率。可是分区+强制过滤并无收到预期的性能效果,可是将一样的查询条件直接在DB中执行却很是快。经过对比执行计划发现,经过页面调用执行时,并无命中分区,而在访问谓词中,log_date字段过滤时,多出了函数INTERNAL_FUNCTION。也就是将log_date字段隐式强制转换成了timestamp。而致使这种问题的缘由是JAVA数据类型与ORACLE数据类型之间的转换出现了问题。最后经过JAVA传STRING到ORACLE,而后在SQL中将变量值TO_DATE成DATE类型解决。
咱们也能够简单模拟下。好比咱们在EMP中建立基于HIREDATE的索引:
CREATEINDEX EMP_I2 ON EMP(HIREDATE);
咱们如今要查找今年以来入职的员工信息,SQL以下:
SELECT*FROM EMP WHERE HIREDATE >SYSDATE
其执行计划以下:
而若是咱们将date’2016-01-01’转换成timestamp,则执行计划以下:
在这个案例中,若是不查看谓词,就很难找到性能的根源。
朴实的执行计划
咱们继续执行计划中的“谓词”,看看还能给咱们哪些意外之喜?
在上个章节中,咱们注意到,在查询今年入职的员工信息是,咱们用了DATE’2016-01-01’。这种写法不多见诸于正式书籍中,由于这是非标准写法。那么将VARCHAR2转换成DATE的标准写法是什么呢?
执行计划会告诉你:
原来DATE’2016-01-01’被转换成了TO_DATE(‘2016-01-01’, ‘SYYYY-MM-DDHH24:MI:SS’),这样是为何DATE只能支持YYYY-MM-DD格式的字符串的缘由:
可见,ORACLE优化器会将SQL中一些非标准的写法转换成标准的朴实的写法。有的时候,最朴实的写法,最容易让人理解。
好比,当你拿到以下SQL时:
SELECT ENAME, SAL
FROM EMP
WHERE SAL >SOME(SELECT SAL FROM EMP WHERE DEPTNO =10);
你会不会很懵菜?会不会去查资料,研究SOME的做用和用法?或许大半天后,你仍然被SOME\ANY\ALL弄得云山雾罩的。
如今,咱们试着从执行计划去探究>SOME的含义。
咱们将子查询替换成具体的LIST(100,200,300),发如今执行计划中,谓词变成了SAL > 100,意思就是大于最小值。换言之,原来的SQL就是要查询大于DEPTNO=10部门最低工资的员工信息。