设为首页 收藏本站
查看: 502|回复: 0

[经验分享] Oracle性能分析4:数据访问方法之全扫描

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2014-9-10 08:57:41 | 显示全部楼层 |阅读模式
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  

该查询的执行计划如下:
[plain] view plaincopy

    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字段的分布如下:
[plain] view plaincopy

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

而T2表中id字段的分布如下:
[plain] view plaincopy

    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)多块读取大小超过了操作系统限制
这时取决于你操作系统,因此是可变的。
高水位线

所谓高水位线,就是表中最后一块有数据写入的数据块。需要注意的是即使几乎所有数据行都被删除了,并且一些块实际上已经完全变为空的了,高水位线还是保持不变。看下面的例子,当表创建并插入数据后:
SouthEast.jpg
而随着后面数据的变化(删除和修改),表中的数据变化为:
SouthEast.jpg
虽然很多存储区域已经没有数据,但高水位线任然保持不变。
那么,高水位线对全扫描会造成什么影响呢?
执行全扫描时,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信息为:
[plain] view plaincopy

    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信息为:
[plain] view plaincopy

    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为原表。




运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-24610-1-1.html 上篇帖子: Oracle性能分析3:TKPROF介绍 下篇帖子: Oracle性能分析5:数据访问方式之索引结构和扫描方式介绍 Oracle
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表