ORACLE将执行过的SQL语句存放在内存的共享池(sharedbufferpool)中,可以被所有的数据库用户共享。当你执行一个SQL语句(有时被称为一个游标)时,如果它和之前的执行过的语句完全相同,ORACLE就能很快获得已经被解析的语句以及最好的执行路径.这个功能大大地提高了SQL的执行性能并节省了内存的使用。
为了不重复解析相同的SQL语句,在第一次解析之后,Oracle将SQL语句存放在内存中。这块位于系统全局区域SGA(systemglobalarea)的共享池(sharedbufferpoo1)中的内存可以被所有的数据库用户共享。因此,当你执行一个SQL语句(有时被称为一个游标)时,如果它和之前执行过的语句完全相同,Oracle就能很快获得已经被解析的语句以及最好的执行方案。Oracle的这个功能大大地提高了SQL的执行性能并节省了内存的使用。
可惜的是,Oracle只对简单的表提供高速缓冲(cachebufferiIlg),这个功能并不适用于多表连接查询。数据库管理员必须在启动参数文件中为这个区域设置合适的参数,当这个内存区域越大,就可以保留更多的语句,当然被共享的可能性也就越大了。当向Oracle提交一个SQL语句时,Oracle会首先在这块内存中查找相同的语句。
要使用内存中共享池的SQL,必须满足以下条件:当前被执行的语句和共享池中的语句必须完全相同(包括大小写、空格、换行等),两个语句所指的对象必须完全相同(同义词与表是不同的对象)两个SQL语句中必须使用相同的名字的绑定变量(bindvariables)。Oracle对两者采取的是一种严格匹配策略,要达成共享。SQL语句必须完全相同(包括空格、换行等)。
能够使用共享的语句必须满足三个条件:①字符级的比较。当前被执行的语句和共享池中的语句必须完全相同。例如:SELECT*FROMATABLE;和下面每一个SQL语句都不同:SELECT*fromATABLE;Select*FromAtable;②语句所指对象必须完全相同即两条SQL语句操作的数据库对象必须同一。③语句中必须使用相同命名的绑定变量。如:第一组的两个SQL语句是相同的,可以共享;而第二组中两个语句不同,即使在运行时赋予不同的绑定变量以相同的值:●第一组selectpin,namefrompeoplewherepin=:blk1.pin;selectpin,namefrompeoplewherepin=:blk1.pin;●第二组selectpin,namefrompeoplewherepin=:blk1.ot_jnd;selectpin,namefrompeoplewherepin=:blk1.ov_jnd;
为什么要绑定变量?
下面这个语句每执行一次就需要在SHAREPOOL硬解析一次,一百万用户就是一百万次,消耗CPU和内存,如果业务量大,很可能导致宕库……如果绑定变量,则只需要硬解析一次,重复调用即可。select*fromdConMsgwherecontract_no=32013484095139
硬解析即整个SQL语句的执行需要完完全全的解析,生成执行计划。而硬解析,生成执行计划需要耗用CPU资源,以及SGA资源。在此不得不提的是对库缓存中闩的使用。闩是锁的细化,可以理解为是一种轻量级的串行化设备。当进程申请到闩后,则这些闩用于保护共享内存的数在同一时刻不会被两个以上的进程修改。在硬解析时,需要申请闩的使用,而闩的数量在有限的情况下需要等待。大量的闩的使用由此造成需要使用闩的进程排队越频繁,性能则逾低下。
绑定变量解决硬解析问题
未使用绑定变量的语句sprintf(sqlstr,"insertintoscott.test1(num1,num2)values(%d,%d)",n_var1,n_var2);EXECSQLEXECUTEIMMEDIATE:sqlstr;EXECSQLCOMMIT;使用绑定变量的语句strcpy(sqlstr,"insertintotest(num1,num2)values(:v1,:v2)");EXECSQLPREPAREsql_stmtFROM:sqlstr;EXECSQLEXECUTEsql_stmtUSING:n_var1,:n_var2;EXECSQLCOMMIT;
SQL优化
目标:
设计方面:
编码方面:
随着数据库中数据的增加,系统的响应速度就成为目前系统需要解决的最主要的问题之一。系统优化中一个很重要的方面就是SQL语句的优化。对于大量数据,劣质SQL语句和优质SQL语句之间的速度差别可以达到上百倍,对于一个系统不是简单地能实现其功能就可,而是要写出高质量的SQL语句,提高系统的可用性。在多数情况下,Oracle使用索引来更快地遍历表,优化器主要根据定义的索引来提高性能。如果在SQL语句的where子句中写的SQL代码不合理,就会造成优化器删去索引而使用全表扫描,一般就这种SQL语句就是所谓的劣质SQL语句。在编写SQL语句时我们应清楚优化器根据何种原则来使用索引,这有助于写出高性能的SQL语句。SQL语句的编写原则和SQL语句的优化,请跟我一起学习以下几方面:
fromuser_filesuf,df_money_filesdm,
cw_charge_recordcc
where
uf.user_no=dm.user_no
anddm.user_no=cc.user_no
and……
andnotexists(select…)
很难优化,随着数据量的增加性能的风险很大。
通常,带有UNION,MINUS,INTERSECT的SQL语句都可以用其他方式重写。
低效:
SELECTDISTINCTDEPT_NO,DEPT_NAME
FROMDEPTD,EMPE
WHERED.DEPT_NO=E.DEPT_NO
高效:
SELECTDEPT_NO,DEPT_NAME
FROMDEPTD
WHEREEXISTS(SELECT‘X’
FROMEMPE
WHEREE.DEPT_NO=D.DEPT_NO);
举例:
低效:
SELECTACCT_NUM,BALANCE_AMT
FROMDEBIT_TRANSACTIONS
WHERETRAN_DATE=’31-DEC-95’
UNION
UNIONALL
SELECTENAME
FROMEMP
WHEREEMPNO=2326
ANDDEPTNO=20;
这里,只有EMPNO上的索引是唯一性的,所以EMPNO索引将用来检索记录.
TABLEACCESSBYROWIDONEMP
INDEXUNIQUESCANONEMP_NO_IDX
如果索引是建立在多个列上,只有在它的第一个列(leadingcolumn)被where子句引用时,优化器才会选择使用该索引。
SQL>createtablemultiindexusage(indanumber,indbnumber,descrvarchar2(10));
Tablecreated.
SQL>createindexmultindexonmultiindexusage(inda,indb);
Indexcreated.
SQL>setautotracetraceonly
SQL>select*frommultiindexusagewhereinda=1;
ExecutionPlan
----------------------------------------------------------
0SELECTSTATEMENTOptimizer=CHOOSE
10TABLEACCESS(BYINDEXROWID)OF'MULTIINDEXUSAGE'
21INDEX(RANGESCAN)OF'MULTINDEX'(NON-UNIQUE)
SQL>select*frommultiindexusagewhereindb=1;
10TABLEACCESS(FULL)OF'MULTIINDEXUSAGE'
很明显,当仅引用索引的第二个列时,优化器使用了全表扫描而忽略了索引。
WHERE子句中,如果索引列是函数的一部分。优化器将不使用索引而使用全表扫描。
SELECT…
FROMDEPT
WHERESAL*12>25000;
WHERESAL>25000/12;
WHERE子句中,如果索引列所对应的值的第一个字符由通配符(WILDCARD)开始,索引将不被采用。
SELECTUSER_NO,USER_NAME,ADDRESS
FROMUSER_FILES
WHEREUSER_NOLIKE'%109204421';
在这种情况下,ORACLE将使用全表扫描。
低效:(这里,不使用索引)
WHEREDEPT_CODE<>0;
高效:(这里,使用了索引)
WHEREDEPT_CODE>0;
如果唯一性索引建立在表的A列和B列上,并且表中存在一条记录的A,B值为(123,null),ORACLE将不接受下一条具有相同A,B值(123,null)的记录(插入).然而如果所有的索引列都为空,ORACLE将认为整个键值为空而空不等于空.因此你可以插入1000条具有相同键值的记录,当然它们都是空!
因为空值不存在于索引列中,所以WHERE子句中对索引列进行空值比较将使ORACLE停用该索引.
任何在where子句中使用isnull或isnotnull的语句优化器是不允许使用索引的。
假设EMP_TYPE是一个字符类型的索引列.
WHEREUSER_NO=109204421
这个语句被ORACLE转换为:
WHERETO_NUMBER(USER_NO)=109204421
因为内部发生的类型转换,这个索引将不会被用到!
SELECTCOUNT(*),SUM(SAL)
WHEREDEPT_NO=0020
ANDENAMELIKE‘SMITH%’;
WHEREDEPT_NO=0030
你可以用DECODE函数高效地得到相同结果
SELECTCOUNT(DECODE(DEPT_NO,0020,’X’,NULL))D0020_COUNT,
COUNT(DECODE(DEPT_NO,0030,’X’,NULL))D0030_COUNT,
SUM(DECODE(DEPT_NO,0020,SAL,NULL))D0020_SAL,
SUM(DECODE(DEPT_NO,0030,SAL,NULL))D0030_SAL
FROMEMPWHEREENAMELIKE‘SMITH%’;
例如:
低效
SELECTTAB_NAME
FROMTABLES
WHERETAB_NAME=(SELECTTAB_NAME
FROMTAB_COLUMNS
WHEREVERSION=604)
ANDDB_VER=(SELECTDB_VER
高效
WHERE(TAB_NAME,DB_VER)
=(SELECTTAB_NAME,DB_VER)
最好不要在WHERE子句中使用函或表达式,如果要使用的话,最好统一使用相同的表达式或函数,这样便于以后使用合理的索引。
ORDERBY语句决定了Oracle如何将返回的查询结果排序。Orderby语句对要排序的列没有什么特别的限制,也可以将函数加入列中(象联接或者附加等)。任何在Orderby语句的非索引项或者有计算表达式都将降低查询速度。仔细检查orderby语句以找出非索引项或者表达式,它们会降低性能。解决这个问题的办法就是重写orderby语句以使用索引,也可以为所使用的列建立另外一个索引,同时应绝对避免在orderby子句中使用表达式。
对于有联接的列,即使最后的联接值为一个静态值,优化器是不会使用索引的。
select*fromemployss
first_name||''||last_name='BeillCliton';
系统优化器对基于last_name创建的索引没有使用。
当采用下面这种SQL语句的编写,Oracle系统就可以采用基于last_name创建的索引。
select*fromemployeewherefirst_name='Beill'andlast_name='Cliton';
通配符(%)在搜寻词首出现,Oracle系统不使用last_name的索引。
select*fromemployeewherelast_namelike'%cliton%';
在很多情况下可能无法避免这种情况,但是一定要心中有底,通配符如此使用会降低查询速度。然而当通配符出现在字符串其他位置时,优化器就能利用索引。在下面的查询中索引得到了使用:
select*fromemployeewherelast_namelike'c%';
避免使用HAVING子句,HAVING只会在检索出所有记录之后才对结果集进行过滤。这个处理需要排序,总计等操作。如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销。
SELECTREGION,AVG(LOG_SIZE)
FROMLOCATION
GROUPBYREGION
HAVINGREGIONREGION!=‘SYDNEY’
ANDREGION!=‘PERTH’
WHEREREGIONREGION!=‘SYDNEY’
顺序
WHERE>GROUP>HAVING
在子查询中,NOTIN子句将执行一个内部的排序和合并.无论在哪种情况下,NOTIN都是最低效的(因为它对子查询中的表执行了一个全表遍历)。
使用NOTEXISTS子句可以有效地利用索引。尽可能使用NOTEXISTS来代替NOTIN,尽管二者都使用了NOT(不能使用索引而降低速度),NOTEXISTS要比NOTIN查询效率更高。
deptnoNOTIN(SELECTdeptnoFROMemp);
NOTEXISTS
(SELECTdeptnoFROMempWHEREdept.deptno=emp.deptno);
2要比1的执行性能好很多。
索引是表的一个概念部分,用来提高检索数据的效率,ORACLE使用了一个复杂的自平衡B-tree结构.通常,通过索引查询数据比全表扫描要快.当ORACLE找出执行查询和Update语句的最佳路径时,ORACLE优化器将使用索引.同样在联结多个表时使用索引也可以提高效率.另一个使用索引的好处是,它提供了主键(primarykey)的唯一性验证。
WHERE子句中,如果索引列是函数的一部分.优化器将不
使用索引而使用全表扫描.
SELECT…FROMDEPTWHERESAL*12>25000;
SELECT…FROMDEPTWHERESAL>25000/12;
SELECT*
WHEREDEPTNO>=4
WHEREDEPTNO>3
例子:
虽然这两种查询的结果一样,但是第二种查询方案会比第一种查询方案更快些。第二种查询允许Oracle对salary列使用索引,而第一种查询则不能使用索引。
外部联接"+"按其在"="的左边或右边分左联接和右联接。若不带"+"运算符的表中的一个行不直接匹配于带"+"预算符的表中的任何行,则前者的行与后者中的一个空行相匹配并被返回。利用外部联接"+",可以替代效率十分低下的notin运算,大大提高运行速度。例如,下面这条命令执行起来很慢:
事务是消耗资源的,大事务还容易引起死锁
COMMIT所释放的资源:
当删除表中的记录时,在通常情况下,回滚段(rollbacksegments)用来存放可以被恢复的信息.如果你没有COMMIT事务,ORACLE会将数据恢复到删除之前的状态(准确地说是恢复到执行删除命令之前的状况)
例如COUNT(EMPNO)
比如有的表PHONE_NO字段是CHAR型,而且创建有索引,
但在WHERE条件中忘记了加引号,就不会用到索引。
WHEREPHONE_NO=‘13920202022’
WHEREPHONE_NO=13920202022
SQL语句的处理过程
语法分析分别执行下列操作:
查询与其它类型的SQL语句不同,因为在成功执行后作为结果将返回数据。
第3步:描述查询结果(DescribeResultsofaQuery)
描述阶段只有在查询结果的各个列是未知时才需要;例如,当查询由用户交互地输入需要输出的列名。在这种情况要用描述阶段来决定查询结果的特征(数据类型,长度和名字)。
第4步:定义查询的输出数据(DefineOutputofaQuery)
在查询的定义阶段,你指定与查询出的列值对应的接收变量的位置、大小和数据类型,这样我们通过接收变量就可以得到查询结果。如果必要的话,Oracle会自动实现数据类型的转换。这是将接收变量的类型与对应的列类型相比较决定的。
第5步:绑定变量(BindAnyVariables)
第8步:取出查询的行(FetchRowsofaQuery)
第9步:关闭游标(ClosetheCursor)SQL语句处理的最后一个阶段就是关闭游标。
Oralce优化器
2种类型的优化器:基于规则的优化器基于代价的优化器。
不同之处:取得代价的方法与衡量代价的大小不同。
基于规则的优化器--RuleBased(Heuristic)Optimization(简称RBO)
基于代价的优化器--CostBasedOptimization(简称CBO)
Oracle把一个代价引擎(CostEngine)集成到数据库内核中,用来估计每个执行计划需要的代价,该代价将每个执行计划所耗费的资源进行量化,从而CBO可以根据这个代价选择出最优的执行计划。查询耗费的资源可以被分成3个基本组成部分:I/O代价、CPU代价、network代价。
Oracle执行计划
为了不重复解析相同的SQL语句(因为解析操作比较费资源,会导致性能下降),在第一次解析之后,ORACLE将SQL语句及解析后得到的执行计划存放在内存中。这块位于系统全局区域SGA(systemglobalarea)的共享池(sharedbufferpool)中的内存可以被所有的数据库用户共享。因此,当你执行一个SQL语句(有时被称为一个游标)时,如果该语句和之前的执行过的某一语句完全相同,并且之前执行的该语句与其执行计划仍然在内存中存在,则ORACLE就不需要再进行分析,直接得到该语句的执行路径。ORACLE的这个功能大大地提高了SQL的执行性能并大大节省了内存的使用。使用这个功能的关键是将执行过的语句尽可能放到内存中,所以这要求有大的共享池(通过设置sharedbufferpool参数值)和尽可能的使用绑定变量的方法执行SQL语句。
Rowid的概念
rowid是一个伪列,既然是伪列,那么这个列就不是用户定义,而是系统自己给加上的。对每个表都有一个rowid的伪列,但是表中并不物理存储ROWID列的值。不过你可以像使用其它列那样使用它,但是不能删除该列,也不能对该列的值进行修改、插入。一旦一行数据插入数据库,则rowid在该行的生命周期内是唯一的,即即使该行产生行迁移,行的rowid也不会改变。
为什么使用Rowid
rowid对访问一个表中的给定的行提供了最快的访问方法,通过ROWID可以直接定位到相应的数据块上,然后将其读到内存。我们创建一个索引时,该索引不但存储索引列的值,而且也存储索引值所对应的行的ROWID,这样我们通过索引快速找到相应行的ROWID后,通过该ROWID,就可以迅速将数据查询出来。这也就是我们使用索引查询时,速度比较快的原因。
在ORACLE8以前的版本中,ROWID由FILE、BLOCK、ROWNUMBER构成。随着oracle8中对象概念的扩展,ROWID发生了变化,ROWID由OBJECT、FILE、BLOCK、ROWNUMBER构成。利用DBMS_ROWID可以将rowid分解成上述的各部分,也可以将上述的各部分组成一个有效的rowid。
RowSource(行源)
用在查询中,由上一操作返回的符合条件的行的集合,即可以是表的全部行数据的集合;也可以是表的部分行数据的集合;也可以为对上2个rowsource进行连接操作(如join连接)后得到的行数据集合。
Predicate(谓词)
一个查询中的WHERE限制条件。DrivingTable(驱动表)
该表又称为外层表(OUTERTABLE)。这个概念用于嵌套与HASH连接中。如果该rowsource返回较多的行数据,则对所有的后续操作有负面影响。注意此处虽然翻译为驱动表,但实际上翻译为驱动行源(drivingrowsource)更为确切。一般说来,是应用查询的限制条件后,返回较少行源的表作为驱动表,所以如果一个大表在WHERE条件有限制条件(如等值限制),则该大表作为驱动表也是合适的,所以并不是只有较小的表可以作为驱动表,正确说法应该为应用查询的限制条件后,返回较少行源的表作为驱动表。在执行计划中,应该为靠上的那个rowsource,后面会给出具体说明。在我们后面的描述中,一般将该表称为连接操作的rowsource1。
ProbedTable(被探查表)
该表又称为内层表(INNERTABLE)。在我们从驱动表中得到具体一行的数据后,在该表中寻找符合连接条件的行。所以该表应当为大表(实际上应该为返回较大rowsource的表)且相应的列上应该有索引。在我们后面的描述中,一般将该表称为连接操作的rowsource2。
组合索引(concatenatedindex)
由多个列构成的索引,如createindexidx_emponemp(col1,col2,col3,……),则我们称idx_emp索引为组合索引。在组合索引中有一个重要的概念:引导列(leadingcolumn),在上面的例子中,col1列为引导列。当我们进行查询时可以使用”wherecol1=”,也可以使用”wherecol1=andcol2=”,这样的限制条件都会使用索引,但是”wherecol2=”查询就不会使用该索引。所以限制条件中包含先导列时,该限制条件才会使用该组合索引。
可选择性(selectivity)
比较一下列中唯一键的数量和表中的行数,就可以判断该列的可选择性。如果该列的”唯一键的数量/表中的行数”的比值越接近1,则该列的可选择性越高,该列就越适合创建索引,同样索引的可选择性也越高。在可选择性高的列上进行查询时,返回的数据就较少,比较适合使用索引查询。
从而找出影响性能的主要问题。举例,如何得到执行计划:
显示下面SQL语句的执行计划。
¨SELECTename,job,sal,dname
¨FROMemp,dept
1、Oracle实现步骤5,并将结果ROWID返回给第4步。2、Oracle实现步骤4,并将结果行返回给第2步。3、Oracle实现步骤2,将接受来自第3步的一行和来自第4步的一行,并返回给第1步一行。4、Oracle实现步骤6,如果有结果行的话,将它返回给第1步。5、Oracle实现步骤1,如果从步骤6返回行,Oracle将来自第2步的行返回给发出SQL语句的用户。
有时语句执行时,并不是象上面说的那样一步一步有先有后的进行,而是可能并行运行,如在实际环境中,3、5、4步可能并行运行,以便取得更好的效率。从上面的树型图上,是很难看出各个操作执行的先后顺序,而通过ORACLE生成的另一种形式的执行计划,则可以很容易的看出哪个操作先执行,哪个后执行,这样的执行计划是我们真正需要的,后面会给出详细说明。现在先来看一些预备知识。
优化器在形成执行计划时需要做的一个重要选择是如何从数据库查询出需要的数据。对于SQL语句存取的任何表中的任何行,可能存在许多存取路径(存取方法),通过它们可以定位和查询出需要的数据。优化器选择其中自认为是最优化的路径。在物理层,oracle读取数据,一次读取的最小单位为数据库块(由多个连续的操作系统块组成),一次读取的最大值由操作系统一次I/O的最大值与multiblock参数共同决定,所以即使只需要一行数据,也是将该行所在的数据库块读入内存。逻辑上,oracle用如下存取方法访问数据:
通过ROWID的表存取(TableAccessbyROWID或rowidlookup)
行的ROWID指出了该行所在的数据文件、数据块以及行在该块中的位置,所以通过ROWID来存取数据可以快速定位到目标数据上,是Oracle存取单行数据的最快方法。为通过ROWID存取表,Oracle首先要获取被选择行的ROWID,或从语句的WHERE子句中得到,或者通过表的一个或多个索引的索引扫描得到。Oracle然后以得到的ROWID为依据定位每个被选择的行。此存取方法不会用到多块读操作,一次I/O只能读取一个数据块。我们经常在执行计划中看到该存取方法,如通过索引查询数据。使用ROWID存取的方法:
!8431bb36e2fa4647aa490996eeaee021.jpg!如果rowsource已经在连接关联列上被排序,则该连接操作就不需要再进行sort操作,这样可以大大提高这种连接操作的连接速度,因为排序是个极其费资源的操作,特别是对于较大的表。预先排序的rowsource包括已经被索引的列(如a.col3或b.col4上有索引)或rowsource已经在前面的步骤中被排序了。尽管合并两个rowsource的过程是串行的,但是可以并行访问这两个rowsource(如并行读入数据,并行排序).排序是一个费时、费资源的操作,特别对于大表。基于这个原因,SMJ经常不是一个特别有效的连接方法,但是如果2个rowsource都已经预先排序,则这种连接方法的效率较高。
SMJ连接的例子:
SQL>explainplanfor
select/*+ordered*/e.deptno,d.deptno
fromempe,deptd
wheree.deptno=d.deptno
orderbye.deptno,d.deptno;
QueryPlan
-------------------------------------
SELECTSTATEMENT[CHOOSE]Cost=17
MERGEJOIN
SORTJOIN
TABLEACCESSFULLEMP[ANALYZED]
TABLEACCESSFULLDEPT[ANALYZED]
嵌套循环(NestedLoops(NL))
NL连接的例子:
selecta.dname,b.sql
fromdepta,empb
wherea.deptno=b.deptno;
-------------------------
SELECTSTATEMENT[CHOOSE]Cost=5
NESTEDLOOPS
哈希连接(HashJoin,HJ)
理论上来说比NL与SMJ更高效,而且只用在CBO优化器中。较小的rowsource被用来构建hashtable与bitmap,第2个rowsource被用来被hansed,并与第一个rowsource生成的hashtable进行匹配,以便进行进一步的连接。Bitmap被用来作为一种比较快的查找方法,来检查在hashtable中是否有匹配的行。特别的,当hashtable比较大而不能全部容纳在内存中时,这种查找方法更为有用。这种连接方法也有NL连接中所谓的驱动表的概念,被构建为hashtable与bitmap的表为驱动表,当被构建的hashtable与bitmap能被容纳在内存中时,这种连接方式的效率极高。要使哈希连接有效,需要设置HASH_JOIN_ENABLED=TRUE,缺省情况下该参数为TRUE,另外,不要忘了还要设置hash_area_size参数,以使哈希连接高效运行,因为哈希连接会在该参数指定大小的内存中运行,过小的参数会使哈希连接的性能比其他连接方式还要低。
HASH连接的例子:
select/*+use_hash(emp)*/empno
fromemp,dept
whereemp.deptno=dept.deptno;
----------------------------
SELECTSTATEMENT[CHOOSE]Cost=3
HASHJOIN
TABLEACCESSFULLDEPT
TABLEACCESSFULLEMP
笛卡儿乘积(CartesianProduct)
当两个rowsource做连接,但是它们之间没有关联条件时,就会在两个rowsource中做笛卡儿乘积,这通常由编写代码疏漏造成(即程序员忘了写关联条件)。笛卡尔乘积是一个表的每一行依次与另一个表中的所有行匹配。在特殊情况下我们可以使用笛卡儿乘积,如在星形连接中,除此之外,我们要尽量避免使用笛卡儿乘积。注意在下面的语句中,在2个表之间没有连接。
在哪种情况下用哪种连接方法比较好:
1.排序--合并连接(SortMergeJoin,SMJ):
需要注意的是,以上方法并不会真正执行sql,只是产生执行计划。
例1:
假设LARGE_TABLE是一个较大的表,且username列上没有索引,则运行下面的语句:
SQL>SELECT*FROMLARGE_TABLEwhereUSERNAME=‘TEST’;
QueryPlan-----------------------------------------
SELECTSTATEMENTOptimizer=CHOOSE(Cost=1234Card=1Bytes=14)
TABLEACCESSFULLLARGE_TABLE[:Q65001][ANALYZED]
在这个例子中,TABLEACCESSFULLLARGE_TABLE是第一个操作,意思是在LARGE_TABLE表上做全表扫描。当这个操作完成之后,产生的rowsource中的数据被送往下一步骤进行处理,在此例中,SELECTSTATEMENT操作是这个查询语句的最后一步。Optimizer=CHOOSE指明这个查询的optimizer_mode,即optimizer_mode初始化参数指定的值,它并不是指语句执行时真的使用了该优化器。决定该语句使用何种优化器的唯一方法是看后面的cost部分。如果给出的是下面的形式,则表明使用的是CBO优化器,此处的cost表示优化器认为该执行计划的代价:
SELECTSTATEMENTOptimizer=CHOOSE(Cost=1234Card=1Bytes=14)假如执行计划中给出的是类似下面的信息,则表明是使用RBO优化器,因为cost部分的值为空,或者压根就没有cost部分。
例2:
假定A、B、C都是不是小表,且在A表上一个组合索引:A(a.col1,a.col2),注意a.col1列为索引的引导列。
selectA.col4fromA,B,CwhereB.col3=10andA.col1=B.col1andA.col2=C.col2andC.col3=5
ExecutionPlan----------------------------------------------------------0SELECTSTATEMENTOptimizer=CHOOSE10MERGEJOIN21SORT(JOIN)32NESTEDLOOPS43TABLEACCESS(FULL)OF'B'53TABLEACCESS(BYINDEXROWID)OF'A'65INDEX(RANGESCAN)OF'INX_COL12A'(NON-UNIQUE)71SORT(JOIN)87TABLEACCESS(FULL)OF'C'
Statistics----------------------------------------------------------0recursivecalls8dbblockgets6consistentgets0physicalreads340redosize551bytessentviaSQL*Nettoclient430bytesreceivedviaSQL*Netfromclient2SQL*Netroundtripsto/fromclient2sorts(memory)0sorts(disk)6rowsprocessed
在表做连接时,只能2个表先做连接,然后将连接后的结果作为一个rowsource,与剩下的表做连接,在上面的例子中,连接顺序为B与A先连接,然后再与C连接:
B<--->A<--->C
在执行计划中,需要知道哪个操作是先执行的,哪个操作是后执行的,这对于判断哪个表为驱动表有用处。判断之前,如果对表的访问是通过rowid,且该rowid的值是从索引扫描中来得,则将该索引扫描先从执行计划中暂时去掉。然后在执行计划剩下的部分中,判断执行顺序的指导原则就是:最右、最上的操作先执行。具体解释如下:
得到去除妨碍判断的索引扫描后的执行计划:
谈论上下关系时,只对连续的、缩进一致的行有效。
从这个图中我们可以看到,对于NESTEDLOOPS部分,最右、最上的操作是TABLEACCESS(FULL)OF'B',所以这一操作先执行,所以该操作对应的B表为第一个驱动表(外部表),自然,A表就为内部表了。从图中还可以看出,B与A表做嵌套循环后生成了新的rowsource,对该rowsource进行来排序后,与C表对应的排序了的rowsource(应用了C.col3=5限制条件)进行MSJ连接操作。所以从上面可以得出如下事实:B表先与A表做嵌套循环,然后将生成的rowsource与C表做排序—合并连接。
通过分析上面的执行计划,我们不能说C表一定在B、A表之后才被读取,事实上,B表有可能与C表同时被读入内存,因为将表中的数据读入内存的操作可能为并行的。事实上许多操作可能为交叉进行的,因为ORACLE读取数据时,如果就是需要一行数据也是将该行所在的整个数据块读入内存,而且还有可能为多块读。
看执行计划时,我们的关键不是看哪个操作先执行,哪个操作后执行,而是关键看表之间连接的顺序(如得知哪个为驱动表,这需要从操作的顺序进行判断)、使用了何种类型的关联及具体的存取路径(如判断是否利用了索引)在从执行计划中判断出哪个表为驱动表后,根据我们的知识判断该表作为驱动表(就像上面判断ABC表那样)是否合适,如果不合适,对SQL语句进行更改,使优化器可以选择正确的驱动表。
基于代价的优化器在绝大多数情况下它会选择正确的优化器,减轻了DBA的负担。但有时它也聪明反被聪明误,选择了很差的执行计划,使某个语句的执行变得奇慢无比。此时就需要DBA进行人为的干预,告诉优化器使用我们指定的存取路径或连接类型生成执行计划,从而使语句高效的运行。例如:对于一个特定的语句,执行全表扫描要比执行索引扫描更有效,则我们可以指示优化器使用全表扫描。在ORACLE中,是通过为语句添加hints(提示)来实现干预优化器优化的目的。
hints是oracle提供的一种机制,用来告诉优化器按照我们的告诉它的方式生成执行计划。可以用hints来实现: