Oracle性能分析4:数据访问方法之全扫描

SQL语句执行时,Oracle的优化器会根据统计信息确定表的访问方式,一般来说,有两种基本的数据访问方式:1)全扫描。在全扫描(全表扫描或者快速全索引扫描)中,多个块被读入到一个IO运算中。2)索引扫描。索引扫描首先扫描索引叶子块以取得特定的行id(rowid),然后利用这些行id来访问父表取得实际的行数据,访问通过单块读取来完成。这里主要讲解全扫描方式,后面将介绍索引扫描。

使用全扫描

当对一个表进行全扫描时,会将表中所有数据块(block)取出并进行处理,筛选出符合条件的数据。注意Oracle必须将整个数据块(block)中的数据读到内存中,再取得符合条件的数据。因此Oracle的优化器需要关心两个信息:获取块的数量和每个块中舍弃的数据量。优化器将根据这两个信息来判断是否使用全扫描,首先我们来看看获取块的数量怎么影响优化器的选择。

获取块的数量

总的来说,如果查询需要取出表的大部分数据块,则应该采用全扫描。但由于很难评估查询将取出的表的数据块的数量,因此在使用全扫描上存在很多这样的“经验法则”:当你的查询会取出表中x%的数据行,则应该选择全扫描。这些法则有一定的道理,但是并不准确,因为当取出的数据行较大时,自然取出的数据块也会较大,这时采用全扫描并没有问题,但有时虽然取出的数据行较小,会取出的数据块也可能会较大,实际上这时也应该采用全扫描,但这些“经验法则”则不再生效。我们看看下面具体的例子。

我们创建一个表T1:

create table t1 as
select trunc((rownum - 1) / 100) id, rownum value
  from dba_source
 where rownum <= 10000

然后为T1创建索引:

create index idx_t1_id on t1(id)

然后为T1收集统计信息:

BEGIN
  dbms_stats.gather_table_stats(user,
                                't1',
                                method_opt => 'FOR ALL COLUMNS SIZE 1',
                                cascade    => TRUE);
END;

然后我们执行查询:

select * from t1 where id = 0

该查询的执行计划如下:

SELECT STATEMENT, GOAL = ALL_ROWS
 TABLE ACCESS BY INDEX ROWID
  INDEX RANGE SCAN	

该执行计划使用了索引范围扫描,由于符合条件id为0的数据在表中只有100行数据,而整个表有1万行数据,查询出的数据只占整个数据的1%,因此我们认为这是一种合理的执行计划。

接下来我们看下面的例子,创建一个表格T2:

create table t2 as
select mod(rownum,100) id, rownum value
  from dba_source
 where rownum <= 10000

同样为T2创建索引:

create index idx_t2_id on t2(id)

然后为T2收集统计信息:

BEGIN
  dbms_stats.gather_table_stats(user,
                                't2',
                                method_opt => 'FOR ALL COLUMNS SIZE 1',
                                cascade    => TRUE);
END;

然后执行查询:

select * from t2 where id = 0

该查询的执行计划如下:

SELECT STATEMENT, GOAL = ALL_ROWS
 TABLE ACCESS FULL

我们看到表的执行计划变成的全表扫描,我们可以很容易的得到该查询的结果任然是100条数据,占T2表总数据量的1%,如果我们进一步比较T2和T1的数据,会发现两张表的id字段完全一样,那为什么T1选择的是索引扫描,而T2却选择了全表扫描呢?

要了解原因,我们需要从数据在数据块上的分布来分析,在T1表中,id字段的分布如下:

0 0...0 0 1 1...1 1 2 2...2 2......88 88...88 88......99 99...99 99

而T2表中id字段的分布如下:

0 1 2 3 ... 98 99 0 1 2 3 ... 98 99 ...... 0 1 2 3 ... 98 99

从这里可以看出T1表中id为0的数据都集中在几个数据块上,而T2表中id为0的数据则分布在很多不同的块上,这样导致T1的查询只需要读取很少块就可以得到结果,因此使用了索引范围扫描,而T2上的查询则需要读取大部分块,因此优化器选择了全表扫描。

舍弃

需要注意的是,全扫描的效率不仅取决于读取的数据块个数,也取决于最终的结果集行数。从上面的例子中我们可以看到:当一个数据块被读取后,查询将根据过滤条件舍弃不符合条件的数据。而这个舍弃的过程是需要耗费资源的,由于这个操作在内存中,因此耗费的将是CPU资源,而舍弃的数据量越大,耗费的CPU资源就越多。

因此,读取的数据块的个数越多,舍弃的数据量越大,全扫描的成本(cost)就越高。

不难想象,当表的数据量不断增大,舍弃的行的数量不断增加,全扫描的成本不断增加,最终可能导致优化器放弃全扫描,转而选择索引扫描。

多块读取方式

多扫描使用的是多块读取,即一个单独的IO调用将会读取多个块,读取的块的数量是可变的,但有一个上限,通过db_file_multiblock_read_count参数指定,该参数通过下面的SQL查看:

select * from v$parameter where name = 'db_file_multiblock_read_count'

下面描述了Oracle在几种情况下读取的块的数量:

1)Oracle不得不读取超过一定边界范围的数据块。

在这种情况下,Oracle将会在一次调用中读取直到边界范围的数据块,然后发起另一次调用来读取剩下的块。

2)存在块已经在内存中

首先读取那么已经在内存中的块,然后发起调用读取剩下的块,这意味着多块读取可能一次仅读取一块。例如,假定多块读取的上限是16,该次读取的数据块编号为1-16,并且编号为偶数的块已经在内存中,那么在这里例子中,将会有8次的单块读取调用来读取奇数编号的块。

3)多块读取大小超过了操作系统限制

这时取决于你操作系统,因此是可变的。

高水位线

所谓高水位线,就是表中最后一块有数据写入的数据块。需要注意的是即使几乎所有数据行都被删除了,并且一些块实际上已经完全变为空的了,高水位线还是保持不变。看下面的例子,当表创建并插入数据后:

而随着后面数据的变化(删除和修改),表中的数据变化为:

虽然很多存储区域已经没有数据,但高水位线任然保持不变。

那么,高水位线对全扫描会造成什么影响呢?

执行全扫描时,Oracle将一直读取到位于表中高水位线的数据块,即使它们是空的,这就意味着许多实际上不需要读取的数据块也被读取了。

下面通过一个具体的实例来看,使用先前的表T2。

1)通过下面的语句判断表所包含的数据块数量:

select blocks from user_segments where segment_name = 'T2'
结果:24

2)确定表中有多少数据块包含数据;

select count(distinct(dbms_rowid.rowid_block_number(rowid))) block_ct from t2
结果:17

3)执行下面的查询,并查看trace信息(trace信息的获取方面见Oracle性能分析1)

alter system flush buffer_cache;--清理缓存
select * from t2 where id = 0

trace信息为:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.06         18         20          0         100
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.07         18         20          0         100

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 5  

Rows     Row Source Operation
-------  ---------------------------------------------------
    100  TABLE ACCESS FULL T2 (cr=20 pr=18 pw=0 time=35975 us)

查询出100行数据,物理读取的数据块数量(disk)为18,包括一个表头数据块的读取(只有17个数据块包含数据)。执行计划使用了全表扫描。

4)执行删除数据的操作

delete from  T2

5)重新获取表包含的数据块数量

select blocks from user_segments where segment_name = 'T2'
结果:24

6)获取包含数据的数据块数量

select count(distinct(dbms_rowid.rowid_block_number(rowid))) block_ct from t2
结果:0

7)执行查询并查看trace信息

alter system flush buffer_cache;--清理缓存
select * from t2 where id = 0

trace信息为:

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.21         18         20          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.22         18         20          0           0

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 5  

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS FULL T2 (cr=20 pr=18 pw=0 time=214806 us)

我们可以看到查询出的行数是0,但任然物理读取了18个数据块,执行计划任然使用了全扫描。

修正高水位线

我们已经了解了高水位线给全扫描带来的性能问题,下面介绍了几种降低高水位线的方法。

使用truncate操作

truncate table_name

在删除数据时尽量使用truncate操作,降低高水位线。

move操作

alter table table_name move

注意move操作需要使用额外的表空间存储,会锁住表,这样其他并发的用户在表上执行的DML语句会产生等待。move操作会影响到表上的索引,因此索引需要rebuild。

shrink操作

shrink space操作,不需要任何额外的空间,但是速度要比move慢上很多。shrink命令分为下面两种:

1)只压缩空间不调整水位线,在业务繁忙时可以执行

alter table table_name shrink space compact

compact操作通过一系列insert、delete操作,将数据尽量排列在段的前面。在这个过程中需要在表上加RX锁,即只在需要移动的行上加锁。但由于涉及到rowid的改变,因此需要enable row movement。

2)调整水位线  会产生锁,可以在业务比较少的时候执行,oracle 会记住1步骤中的操作,只调整水位线

alter table big_table shrink space

使用新表

复制要保留的数据到临时表t,drop原表,然后rename临时表t为原表。

时间: 2024-10-08 09:29:40

Oracle性能分析4:数据访问方法之全扫描的相关文章

Oracle性能分析7:索引的使用

这一节主要讲述索引的使用,首先介绍怎么在查询中避免使用索引,然后介绍优化器怎么判断是否使用索引,并介绍了强制使用索引的方法,最后介绍了Oracle的并行处理方法. 避免使用索引 虽然你创建了索引,但有些查询你可能需要避免使用这些索引,或者你为了做一些测试,希望看看各种情况下查询的情况,也希望能够避免使用一些索引或者索引扫描方式.Oracle提供了方式来达到这些目地,就是在查询中使用hint信息,具体情况如下. 避免使用某个索引 如果索引的选择性很差,那么也许使用其它索引或者使用全表扫描的效率会更

Oracle性能分析12:对象统计信息

对象统计信息描述数据是如何在数据库中存储的,查询优化器使用这些统计信息来做出正确的决定.Oracle中有三种类型的对象统计信息:表统计.列统计和索引统计.而在每种类型中,有细分为:表或索引级别的统计.分区级别统计和子分区级别的统计,后面两种只有在对象被分区和具有子分区的情况下才可用. 统计信息相关视图 表统计信息 表/索引级别的统计 user_tab_statistics user_tables 分区级别的统计 user_tab_statistics user_tab_partitions 子分

Oracle性能分析工具介绍及使用

oracle数据库级别优化分析工具介绍 当我们对数据库优化诊断时,需要收集相应的信息以供参考,从个人的使用经验来说,这种统计数据分为两大类 一类是数据库级别的统计信息二类是os级别的统计信息 下面就分别介绍在不同的级别下,常用什么工具来收集信息帮助优化诊断 首先是oracle数据库级别优化分析工具介绍 目录: 1.statspack2.ASH3.AWR4.ORACLE EXPLAIN PLAN的总结(查询sql的执行计划)   a.autotrace   b.explain的使用 1.stats

Oracle性能分析3:TKPROF介绍

tkprof是Oracle自带的一个命令行工具,主要作用是将原始的跟踪文件转换为格式化的文本文件,最简单的使用方式如下: tkprof ly_ora_128636.trc ly_ora_128636.txt tkprof带有很多参数,在多数情况下,使用这些参数对你的分析将很有帮助. tkprof参数 如果不带任何参数运行tkprof,它将打印出完整的参数列表,并带有简单的描述.下面是对参数的说明: explain 为每个SQL语句提供一个执行计划.该参数需要指定用户.密码,也可以指定数据库连接串

Oracle性能分析1:开启SQL跟踪和获取trace文件

当Oracle查询出现效率问题时,我们往往需要了解问题所在,这样才能针对问题给出解决方案.Oracle提供了SQL执行的trace信息,其中包含了SQL语句的文本信息,一些执行统计,处理过程中的等待,以及解析阶段(如生成执行计划)产生的信息.这些信息有助于你分解sql语句的服务时间和等待时间,并了解所用资源和同步点的详细信息,从而帮助你诊断存在的性能问题. 这篇文章介绍了怎么开启SQL跟踪和获取trace文件,详细信息如下. 开启SQL跟踪 从内部技术细节看,SQL跟踪是基于10046调试事件的

【转】oracle 中随机取数据的方法

oracle 中随机取数据的方法: 1.快速随机取数据(推荐使用): select * from MEMBER sample(1) where rownum <= 10 2.随机取数据,较慢 select * from (  select * from MEMBER order by dbms_random.value) where rownum<=10 ========原文======== 最近在做系统时用到了随机抽取记录的问题: 上网上查找了很多相关资料,发现了不同的方法及其差异.都是基于

Oracle性能分析2:trace文件解读

下面是trace文件中的一个片段,表示一个SQL执行的过程,一个trace文件由很多这样的片段组成: PARSING IN CURSOR #4 len=135 dep=1 uid=0 oct=3 lid=0 tim=777069789359 hv=1115215392 ad='33e7e384' select /*+ index(idl_char$ i_idl_char1) +*/ piece#,length,piece from idl_char$ where obj#=:1 and part

Oracle性能分析6:数据访问方式之索引扫描

这节将介绍各种索引扫描方式,在了解了各种索引扫描方式的特点后,你就可以判断你的执行计划中使用的扫描方式是否正确,并可以针对获取的信息作出改进. 索引唯一扫描 在下面的场景中使用相等条件时,数据库使用索引唯一扫描. 1)查询条件中包含唯一索引中的所有列时: 2)查询条件使用主键约束列时. 下面是一个实际的例子,在表historyalarm中创建如下唯一索引: create unique index idx_historyalarm$queryid on historyalarm(queryid)

Oracle性能分析5:数据访问方式之索引结构和扫描方式介绍

上篇文章讲述了全扫描,这篇文章将介绍索引的结构和扫描方式,在后面将开始讲述每一种扫描方式. 当Oracle通过索引检索具体的一列或多列的列值时,就会执行索引扫描.首先我们来看看索引节点包含的数据. 索引节点包含的数据 索引可以被创建在表的单列或者多列上,索引中包含了这些列的值.rowid和一些其它信息,我们关心的只有列值和rowid.由于索引带有列值,应此如果你的SQL语句只涉及到索引的列,那么Oracle就只从索引本身检索列值,而不需要访问表.如果查询涉及到索引列以外的列,Oracle就需要使