SQL优化 第三章 表访问方式

参考资料:
本系列博客主要参考资料有CUUG冉乃纲老师数据库教学笔记,《SQL优化核心思想》(罗炳森,黄超,钟侥著),《PostgreSQL技术内幕:查询优化深度探索》(张树杰著),排名不分先后。

 

通过第一章和第二章讲解的基础,我们开始学习怎么从表中获取数据更为高效,也就是走索引还是走全表,因为索引好,也不是万能的,全表扫,有时候性能还要高过索引,所谓,存在即使合理的。同时,我们会从基本理论上讲解在不同场景下,各种访问方式高效和低效的原因。

1 全表扫描

select * from emp where sal>1000;

TABLE ACCESS FULL 就是全表扫描,前面的星号代表有过滤条件。

2 索引扫描

2.1 索引唯一扫描

2.2 索引范围扫描

select * from emp where empno>7788;

索引唯一扫描和索引范围扫描的区别参考第一章<3.1用简单平衡树+双向链表解释索引>。

2.3 index skip scan

首先我们解释什么是index skip scan

drop table test1;

create table test1 as select * from dba_objects ;

create index ix_test1_01 on test1(owner,OBJECT_ID);

analyze table test1 compute statistics for table for all columns for all indexes;

select  * from test1 t where t.OBJECT_ID =1000;

简单说,就是检索条件的项目不是索引的前导列,索引扫描时需要走不同的分支。

2.4 index fast full scan

这种索引扫描方式主要是为了把胖表(字段很多)表项目提取出来,做一个瘦表,提取数据时,不用从表里提取,直接从索引提取,以减少IO成本。我在项目中用的并不是太多,之索引拿出来专门说一下,是因为这种扫描方式类似于自己构造了一个瘦表的全表扫描。

drop table test1;

create table test1 as select * from dba_objects ;

create index ix_test1_01 on test1(owner,OBJECT_ID);

select   t.OWNER from test1 t where owner = 'SYS';

2.5 有索引但是优化器不用

有时候,我们分明建了索引,但是优化器就是不用,比如以下案例

drop table test1;

create table test1 as select * from dba_objects ;

create index ix_test1_01 on test1(OBJECT_NAME);

select   * from test1 t where TRIM(OBJECT_NAME)='I_IND1';

有些开发者喜欢加个TRIM啥的,初衷是为了增加程序强壮性,避开空格啥的造成程序错误,但是在索引使用中就不要这么用了,我们回顾以下索引结构,

索引里存储的键值对,键是项目值,值是rowid,在检索条件里做了一些处理,或者发生类型转换,哪怕是隐式类型转换,都有可能造成SQL不走索引,如果想用索引,最好保证条件和索引里面存储的完全一致,包括数据类型。

当然,如果就想在条件里用一些函数进行处理,那么可以考虑函数索引,可以自行调查。

 

3 全表扫描和索引扫描选择

通过之前讲解,我们了解了全表扫描和索引扫描,给我们的认识是检索性能索引高于全表扫,索引影响数据增删改性能,那么为什么还保留着全表扫描呢?难不成只是因为应付没有索引的表?答案是错,全表扫在某些场景下性能更高。我们用案例说话。

drop table test1;

create table test1 as select * from dba_objects ;

create index ix_test1_01 on test1(OBJECT_ID);

select * from test1 t where OBJECT_ID <50000;

select  /*+ index(t ix_test1_01)*/ *

from test1 t where OBJECT_ID <50000;

使用索引的逻辑读比全表扫更大,用索引后IO成本增加了,为啥呢?

关键字是TABLE ACCESS BY INDEX ROWID意思是通过索引不能获取所有想要的数据,比如owner字段等,需要用索引定位出来的rowid去表里找数据,就是回表。

这也不能解释逻辑读为啥还增多了。增多的原因是回表是单块读,比如第一次用rowid找到的数据在block1 上,读一次block1,第二次rowid还在block1上,就再读一次,所以逻辑读增加了,全表扫是多块读,就是把数据从头到尾读一遍,那么就不会造成块的重复读入,所以全表扫这个时候逻辑读小了。

那么问题又来了,索引读给做成多块读不就好了。答案是做不到啊(NDEX FAST FULL SCAN是例外,参照第三章 2.4 index fast full scan)

为啥呢,表里数据无序,索引里数据有序,他们的对应关系是交叉的。如下图

(此图片来自CUUG冉乃纲老师教学笔记)

索引读无法实现回表后多块读。因为索引检索出来的数据,极大可能散落在表的任何一个块里。

索引和表的关系,还有一个参数叫聚簇因子,聚簇因子太大,也可能不走索引,原因也是回表的IO成本高。聚簇因子大家可以在网上调查。

那么问题来了,都有好处,都有坏处,我们怎么选择呢?

其实非常简单,走索引后之所以IO成本高是因为回表造成的,回表少,那么问题就不存在了,数据条数返回少,回表不就少了嘛。结论是,返回数量少,那么可以走索引,至于多少是少,要根据实际环境来判定。

反之,返回多就走全表。

 

4 建索引的方法

首先声明,我们这里讨论的是B树索引,位图索引请自行调查。

关于索引使用,之前我们已经有了个结论,返回数据量少,走索引,数据量大,走全表。

那么,我么该怎么建索引呢?

第一个要求,索引字段要出现在where条件里,或者表连接条件(为了应对嵌套循环连接,后面第五章 表连接方法 讲解)里,(部分情景出现在order by等其他条件里,为了利用索引有序性,使用场景有限,个别调查验证吧,我们不详细说明了)。

第二个要求,表字段重复值要少,也就是去重复后还有很多数据,并且分布均匀,具体可以网上调查<基数><选择性>。

比如dba_objects的object_id字段很适合建立索引,而owner建索引有时候反而引发性能问题,重复值少,分布均匀,那么用索引检索的数据就会少,回表少,减少IO;重复值多,索引检索的数据也多,回表就多;数据不均匀,倾斜大,同时会伴随重复数据多,比如dba_objects的owner=‘SYS’的数据,可能在嵌套循环时引发数据大量翻倍,具体在第六章 查询转换<半连接和内连接转换>有案例讲解。

始于索引的分类,函数索引,反向键索引,组合索引等,希望大家自行调查。

猜你喜欢

转载自blog.csdn.net/songjian1104/article/details/91349929