SQL语句性能调整之ORACLE的执行计划

共享sql语句

Oracle在执行每条sql语句的时候都会先对语句进行语法分析,而这个过程是比较消耗资源的,为了能够略过这个步骤从而提高sql语句的执行效率,oracle采用的共享sql语句的办法,就是把每条从用户发出的sql存储到sga(system global area)系统全局区的共享池当(shared buffer pool)中,这个内存块当中的所有sql语句能够被数据库的所有用户共享;所以当你发出一条sql语句的时候,oracle会先去共享池当中查找是否存在(之所以会有十分存在的说法,是因为这块共享池大小是有限的(一块内存区域),当该共享池被用满后会被后来的重写掉,可以通过修改系统参数来修改该共享池的大小)这么一sql语句的执行计划,如果存在则会直接得到该sql语句的执行计划和执行路径,这么一来就大大节省了系统内存并且提高了sql语句的执行效率;

但是oracle对是否相同的语句的判断是非常严格的,包括发出sql的用户和sql语句的大小写、空格、换行和注释都必须一致才能被认为相同的sql语句;

下面是oracle判断相同的sql语句的执行步骤:

1、

对所发出的语句的文本串进行hasehd[1],如果得到的hash值和共享池中的hash值相等,那么执行第2步骤;

2、

对所发出的语句包括大小写、空格、换行和注释等与第1步骤得到的所有sql语句进行比较;

[1].散列函数(或散列算法)是一种从任何一种数据中创建小的数字“指纹”的方法。该函数将数据打乱混合,重新创建一个叫做散列值的指纹。散列值通常用来代表一个短的随机字母和数字组成的字符串。好的散列函数在输入域中很少出现散列冲突。

大家可能不明白,为什么还分了2个步骤进行,直接进行第2个步骤不就可以了?是的,执行第2个步骤才是真正的比较和验证2个sql语句是否相同的步骤,但是如果oracle针对每条发出的sql都直接从大小写、空格、换行和注释方面进行比较效率不是非常低下的吗?Hash算法可以有效的提高验证的效率。

ROWID

是一个伪列,既然是一个伪列,那么该元素是不存在物理存储当中的,是系统自定义的,所以你无法对其做增、改和删操作,但是你可以像使用普通列一样来使用它,每个表都有一个rowid的伪列。一旦一行数据记录被插入到表中,那么该行的rowid的生命周期是唯一的,即使该行发生了数据迁移;

使用rowid的原因:

大家可能都知道说用索引来访问数据表的数据是最快的,那么索引当中就存放着包含rowid的信息,这样当我们使用索引的时候就能快速找到对应的rowid,Rowid对访问表中的给定的行提供了最快的访问方法,通过rowid可以直接定位到相应的数据块上,然后将其读入内存(内存读取的数据比磁盘的数据N快,这里不讨论这个了)。

ROW SOURCE

行源,返回的符合条件的集合,可以是全表的数据集合,也可以是表中部分数据集合;

Predicate

谓词,where限制条件;

Drivering table

驱动表,也叫做外层表(outer table),驱动表应该选择数据量比较小的表,当然如果一张大表通过限制后能够返回较少的数据也是能够被作为驱动表的;

Probed table

被探查表,也叫做内层表(inner table),从驱动表得到第一条记录后会连接内成表查找符合记录的行,所以一般是大表作为内层表;

Concatenate index

组合索引;

组合索引有如下使用规则:

比如一个组合索引由col1、col2、col3构成,那么该索引的col1称之为该组合

索引的引导列。对于组合索引,必须把引导列放到where条件子句的第1个位置,“where col1=?”和“where col1= ? and col2=?”都会有效的使用到组合索引,但是“where col2=?”则废掉了该组合索引;

索引的选择

列的行记录数量和表中的行记录熟练的比例要尽量等于1;

Oracle为了执行sql语句会做许多工作,这些步骤的工作有可能是从数据库中物理检索数据行,或者使用某种办法准备数据行,以供发出语句的用户使用,oracle用来执行语句的这些步骤就称之为执行计划;

访问路径 access path

优化器在形成计划的时候需要做出一个重要的选择:如何从数据库中查询出所需要的数据。对应sql语句存取任何表中的任何行可能存在多种存取办法(存取路径),通过他们查找和定位出所需要的数据,oracle会选择认为最为优化的办法(路径)。

在物理层,oracle读取都是以块[2](oracle中的块,windows下默认为8k)为单位的,每次读取都是块的整数倍,即使你读取的数据达不到一个块的大小也是读取一个块,那么假使一次读取的数据量很大的时候,是由db_file_multiblock_read_count决定了一次最多能够读取多少块,不管一次读取多少块的数据,都是将其放入内存(再次强调,内存的逻辑读取速度远远快于磁盘的I/O物理读取)。

[2].show parameter blokc可以查看相关的参数信息;

db_block_size integer 8192

db_file_multiblock_read_count integer 16

逻辑上,oracle采用如下的方式读取数据:

A.全表扫描(full table scans,fts)

为了实现全表扫描,oracle读取所有的行,并检查每行是否满足where的限制条件。Oracle顺序的读取分配给该表的所有数据库,一直读到最高水位出(hight weter mark,hwm),多块的读取能够提高全表扫描的效率。

Hwm,delete操作并不会更新hwm,truncate才会使得高水印恢复0,所以现在你知道为什么当要整表删除掉数据的时候使用truncate而不推荐也不应该使用delete的原因了吧???当然你也可以使用手工的方式释放和整理hwm,oracle10g后才支持后,不过现在没什么应用还在10g前吧!

这种模式(fts)读取的数据会被放到高速缓存lru(leasted recent userd,最近最少使用)列表的末尾,这样也能保证经常使用、重要的数据避免被请出内存。

在大表上不建议使用全表扫描,除非你要读取超过5%-10%的数据;

B. Rowed读取(table access by rowed或者rowid lookup)

行的ROWID指出了该行所在的数据文件、数据块以及行在该块中的位置,所以通过ROWID来存取数据可以快速定位到目标数据上,是Oracle存取单行数据的最快方法。

为了通过ROWID存取表,Oracle首先要获取被选择行的ROWID,或者从语句的WHERE子句中得到,或者通过表的一个或多个索引的索引扫描得到。Oracle然后以得到的ROWID为依据定位每个被选择的行。这种存取方法不会用到多块读操作,一次I/O只能读取一个数据块,这点非常关键,所以你以为使用全表扫描一定会比使用索引扫描(索引查找会引发该模式)慢的话,那就大错特错了,记住,该模式一次只读取一个数据库,而全表扫描会连读数据块,对应I/O物理操作来说是非常可观的负担。

C. 索引扫描(index scan或者index lookup)

通过索引查找到rowid(非唯一索引的话可能会得到多个rowid),然后通过rowid得到数据,一个rowid对应一条记录;

该模式分2个I/O步骤实现:先通过索引查找rowid,然后通过rowid查找数据,第一个步骤,由于索引可能经常被使用到,可能会直接的cache得到,速度上应该是快的,第2个步骤,对于大表来说,不可能把所有的数据都放到内存中,所以大部分时间都是磁盘的操作了,这个步骤就会显的N慢,所以如果大表读取的数据量大于5%-10%,那么选择索引扫描是错误的做法了;

细化了说道索引的位置,如

select * from countries;

select countries_id from countries;-- countries_id主键

这2个是有区别的,另外索引列的排序是不需要再次的读取操作的,因为所以已经被排序号了。

根据索引的类型和where子句的不同,索引访问又分为如下4种:

A. 索引唯一扫描(index unique scan)

例如表employees 有index1 id,index2 mail、name

Select * from employess where id=1;

Select * from employess where mail=’a’ and name=’a’;[T]

都会使用索引唯一扫描,但是

Select * from employess where name=’a’;

[T].发现用序号太麻烦了,直接用Tips吧,废话了是吧,呵呵!注意这里要返回的只有一行数据的时候才是唯一扫描。

废了了索引,引导列记得吗,不记得的往上翻吧!

B. 索引范围扫描(index rang scan)

范围扫描一般是使用> < >= <= AND BETWEEN之类的计算的时候产生的,

Select * from employess where id>=1;

Select * from employess where mail=’a’ and name=’a’;--返回了多行

C. 索引全扫描(index full scan)

记住全扫描只有的CBO模式下有效(默认的就是CBO吧,如果没记错的话),optimizer_mode是all_rows的话就是基于成本的了,如下

Select * from employess order by id

D.索引快速扫描(index fast full scan)

返回的只包含索引列的,和全扫描的区别是没有排序

Select id from employess;

产生执行计划

A. set autotrace on;

select * from dual;

B. explain plan for select * from dual;

Select * from plan_table;

C. Select Address, Substr(Sql_Text, 1, 20) Text, Buffer_Gets, Executions, Buffer_Gets / Executions Avg

From V$sqlarea

Where Executions > 0 And Buffer_Gets > 100000

Order By 5;

附带说下sql表的顺序和where条件的顺序,虽然现在的oracle已经非常智能化了,已经能够自动选择最优化的执行计划了。。。唉,DBA啊,现在的活越来越高雅了;驱动表最后,表ORACLE采用自下而上的顺序解析WHERE子句,根据这个原理,表之间的连接必须写在其他WHERE条件之前, 那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾.这个原则也要看是采用RBO还是CBO,对于RBO来说,以from 子句中从右到左的顺序选择驱动表,即最右边的表为第一个驱动表;CBO根据统计信息选择驱动表,假如没有统计信息,则在from 子句中从左到右的顺序选择驱动表。这与RBO选择的顺序正好相反。如果用ordered 提示(此时肯定用CBO),则以from 子句中按从左到右的顺序选择驱动表。

哦,天,上面这段是oracle的官方文档,我~~我平时的判断和执行还是有出入的,嗯,不过现在的optimizer太聪明了,上面的参考就好了,嘻嘻~~~

当然上述只是指导原则,实际上你还的根据执行计划来判断了。

干预执行计划

使用hints干预吧

hints是oracle提供的一种机制,用来告诉优化器按照我们的告诉它的方式生成执行计划。我们可以用hints来实现:

1) 使用的优化器的类型

2) 基于代价的优化器的优化目标,是all_rows还是first_rows。

3) 表的访问路径,是全表扫描,还是索引扫描,还是直接利用rowid。

4) 表之间的连接类型

5) 表之间的连接顺序

6) 语句的并行程度

除了”RULE”提示外,一旦使用的别的提示,语句就会自动的改为使用CBO优化器,此时如果你的数据字典中没有统计数据,就会使用缺省的统计数据。所以建议大家如果使用CBO或HINTS提示,则最好对表和索引进行定期的分析。

如何使用hints:

Hints只应用在它们所在sql语句块(statement block,由select、update、delete关键字标识)上,对其它SQL语句或语句的其它部分没有影响。如:对于使用union操作的2个 sql语句,如果只在一个sql语句上有hints,则该hints不会影响另一个sql语句。

我们可以使用注释(comment)来为一个语句添加hints,一个语句块只能有一个注释,而且注释只能放在SELECT, UPDATE, or DELETE关键字的后面

使用hints的语法:

{DELETE|INSERT|SELECT|UPDATE} /*+ hint [text] [hint[text]]... */

or

{DELETE|INSERT|SELECT|UPDATE} --+ hint [text] [hint[text]]...

例如:

INSERT /*+ APPEND */ INTO。。。。

注解:

1) DELETE、INSERT、SELECT和UPDATE是标识一个语句块开始的关键字,包含提示的注释只能出现在这些关键字的后面,否则提示无效。

2) “+”号表示该注释是一个hints,该加号必须立即跟在”/*”的后面,中间不能有空格。

3) hint是下面介绍的具体提示之一,如果包含多个提示,则每个提示之间需要用一个或多个空格隔开。

4) text 是其它说明hint的注释性文本

如果你没有正确的指定hints,Oracle将忽略该hints,并且不会给出任何错误。

使用全套的hints:

当使用hints时,在某些情况下,为了确保让优化器产生最优的执行计划,我们可能指定全套的hints。例如,如果有一个复杂的查询,包含多个表连接,如果你只为某个表指定了INDEX提示(指示存取路径在该表上使用索引),优化器需要来决定其它应该使用的访问路径和相应的连接方法。因此,即使你给出了一个INDEX提示,优化器可能觉得没有必要使用该提示。这是由于我们让优化器选择了其它连接方法和存取路径,而基于这些连接方法和存取路径,优化器认为用户给出的INDEX提示无用。为了防止这种情况,我们要使用全套的hints,如:不但指定要使用的索引,而且也指定连接的方法与连接的顺序等。

下面是一个使用全套hints的例子,ORDERED提示指出了连接的顺序,而且为不同的表指定了连接方法:

SELECT /*+ ORDERED INDEX (b, jl_br_balances_n1) USE_NL (j b)

USE_NL (glcc glf) USE_MERGE (gp gsb) */

b.application_id, b.set_of_books_id ,

b.personnel_id, p.vendor_id Personnel,

p.segment1 PersonnelNumber, p.vendor_name Name

FROM jl_br_journals j, jl_br_balances b,

gl_code_combinations glcc, fnd_flex_values_vl glf,

gl_periods gp, gl_sets_of_books gsb, po_vendors p

WHERE ...

指示优化器的方法与目标的hints:

ALL_ROWS -- 基于代价的优化器,以吞吐量为目标

FIRST_ROWS(n) -- 基于代价的优化器,以响应时间为目标

CHOOSE -- 根据是否有统计信息,选择不同的优化器

RULE -- 使用基于规则的优化器

例子:

SELECT /*+ FIRST_ROWS(10) */ employee_id, last_name, salary, job_id

FROM employees

WHERE department_id = 20;

SELECT /*+ CHOOSE */ employee_id, last_name, salary, job_id

FROM employees

WHERE employee_id = 7566;

SELECT /*+ RULE */ employee_id, last_name, salary, job_id

FROM employees

WHERE employee_id = 7566;

指示存储路径的hints:

FULL /*+ FULL ( table ) */

指定该表使用全表扫描

ROWID /*+ ROWID ( table ) */

指定对该表使用rowid存取方法,该提示用的较少

INDEX /*+ INDEX ( table [index]) */

使用该表上指定的索引对表进行索引扫描

INDEX_FFS /*+ INDEX_FFS ( table [index]) */

使用快速全表扫描

NO_INDEX /*+ NO_INDEX ( table [index]) */

不使用该表上指定的索引进行存取,仍然可以使用其它的索引进行索引扫描

SELECT /*+ FULL(e) */ employee_id, last_name

FROM employees e

WHERE last_name LIKE :b1;

SELECT /*+ROWID(employees)*/ *

FROM employees

WHERE rowid > 'AAAAtkAABAAAFNTAAA' AND employee_id = 155;

SELECT /*+ INDEX(A sex_index) use sex_index because there are few

male patients */ A.name, A.height, A.weight

FROM patients A

WHERE A.sex = ’m’;

SELECT /*+NO_INDEX(employees emp_empid)*/ employee_id

FROM employees

WHERE employee_id > 200;

指示连接顺序的hints:

ORDERED /*+ ORDERED */

按from 字句中表的顺序从左到右的连接

STAR /*+ STAR */

指示优化器使用星型查询

SELECT /*+ORDERED */ o.order_id, c.customer_id, l.unit_price * l.quantity

FROM customers c, order_items l, orders o

WHERE c.cust_last_name = :b1

AND o.customer_id = c.customer_id

AND o.order_id = l.order_id;

/*+ ORDERED USE_NL(FACTS) INDEX(facts fact_concat) */

指示连接类型的hints:

USE_NL /*+ USE_NL ( table [,table, ...] ) */

使用嵌套连接

USE_MERGE /*+ USE_MERGE ( table [,table, ...]) */

使用排序- -合并连接

USE_HASH /*+ USE_HASH ( table [,table, ...]) */

使用HASH连接

注意:如果表有alias(别名),则上面的table指的是表的别名,而不是真实的表名

对于USE_NL与USE_HASH提示,建议同ORDERED提示一起使用,否则不容易指定那个表为驱动表。

写在最后,后面的几十行没怎么验证,有兴趣的可以去验证下,其实,oracle这么强大的、这么聪明的优化器,一般的都能自动帮我们取得最优化的执行路径,只有当数据非常庞大而且执行非常慢的时候(前几天刚刚处理过了几十亿条的数据,慢的出奇,怕了)可能我们才会去仔细研究这些内容,但是这些知道了总没有坏处吧,and you?!

你可能感兴趣的:(Oracle,Mangement,Soft,Developer,Oracle,Performance,Tunning)