【Oracle性能优化】执行计划与索引类型分析

一条sql的好坏,主要来源两个方面:程序员

  • 一、 从数据库层面:取决于优化器所采用的数据访问方式和数据处理的方式决定
  • 二、从业务方面来说:这条sql在业务上是否是一条好的sql

咱们以oracle 11g为例子进行分析。sql

1、数据的访问方式

【没有索引】数据库

若是一张表没有创建索引,那么优化器采用的数据访问方式也会大相径庭,这就取决于oracle的数据访问方式,下边列举两种:bash

  • 一、并行访问
  • 二、多数据块访问

【有索引】oracle

创建索引的状况,也会有不一样的数据访问方式,主要有下面5种:oop

  • 一、惟一索引(index unique scan)
  • 二、范围索引扫描(index range scan)
  • 三、全索引扫描(index full scan)
  • 四、全索引快速扫描(index fast full scan)
  • 五、索引跳跃扫描(index skip scan)

2、数据的处理方式

上面列举了几种数据的访问方式,其实像咱们平常开发中使用到的排序order by,分组group by、统计count等等操做,都是对数据的一种操做方式,可是,除了这些基本的操做方式以外,咱们通常还会对表进行链接join处理,对于链接这种处理方式,又有下面几种状况:学习

  • 一、nested loop join(内部嵌套循环链接)测试

  • 二、hash join(哈希链接)优化

  • 三、sort merge join(合并排序链接)ui

接下来,咱们使用测试用例验证上面3种join数据处理方式,测试SQL用例以下:

-- 删除nestedLoopTest一、nestedLoopTest2表
drop table nestedLoopTest1 ;
drop table nestedLoopTest2 ;

-- 建立nestedLoopTest表
create table nestedLoopTest1
(
  id    NUMBER(11)
);
commit;

create table nestedLoopTest2
(
  id    NUMBER(11)
);
commit;


-- 各赋值100条数据
BEGIN
FOR i IN 0..100 LOOP
INSERT INTO nestedLoopTest1(id) VALUES(i);
END LOOP;
END;
commit;

BEGIN
FOR i IN 0..100 LOOP
INSERT INTO nestedLoopTest2(id) VALUES(i);
END LOOP;
END;
commit;

-- 一、hash join 哈希链接,由于此时两张表是并行执行的
xxxxxx

-- 二、nested join 内部嵌套链接,此时t2中id建了索引
xxxxxx
-- 三、两个表的id都创建了索引

复制代码
  • 一、hash join(哈希链接)

咱们继续执行下面sql:

select t1.* from nestedLoopTest1 t1,nestedLoopTest2 t2 where t1.id=t2.id;
复制代码

此时表nestedLoopTest1和表nestedLoopTest2中的id都没有创建索引,所以,咱们会看到下面的执行计划:

执行步骤如上图所示,咱们能够看到,此时两张表都是全表扫描,而后再进行一次Hash join,至于hash join的原理,后面单独学习介绍。hash join会将小表load进内存中,而后利用大表和小表进行关联操做

  • 二、nested loop join(内部嵌套循环链接)

咱们继续执行下面sql:

create index nestedLoopTest2index on nestedLoopTest2(id);
select t1.* from nestedLoopTest1 t1,nestedLoopTest2 t2 where t1.id=t2.id;
复制代码

从执行计划中能够看出,首先对表t1进行全表扫描,而后对索引nestedLoopTest2index进行range范围扫描,为何是范围扫描呢?由于表t1中的一条记录,可能在表t2对应多条记录。

对于循环嵌套链接方式,咱们能够想象成2个for循环嵌套便可。

另外,当两个表都创建索引时,咱们再继续执行下面的sql:

create index nestedLoopTest1index on nestedLoopTest1(id);
select t1.* from nestedLoopTest1 t1,nestedLoopTest2 t2 where t1.id=t2.id;
复制代码

相比上图,表t1再也不是全表扫描了,而是全索引扫描。

  • 三、sort merge join(合并排序链接)

该连接方式大概的原理就是,判断原表是否排序,若是未排序,则针对关联字段进行排序;判断关联表是否排序,若是未排序,则进行排序,最后将两个排序的表进行合并。

3、oracle执行计划

咱们要知道oracle数据是如何数据访问和数据处理的,咱们就要看下执行计划,但执行计划又仅仅告诉咱们这些信息。

咱们登录上sqlplus,就拿一条最简单的sql进行说明,简单说下咱们应该如何看懂执行计划:

select * from emp;

emp表是oracle自带的员工表,右键点击Explain Plan,或者按下F5查看执行计划,以下图:

执行计划最左边的Description列是比较重要的,它会列出这个sql的一些执行步骤,该列有下面几个查看规则:

  • 一、层次不一样状况下,越靠右的步骤越先执行;
  • 二、层次相同状况下,越上方的结果越先执行;

上图告诉咱们,这条语句采用的数据访问方式是:TABLE ACCESS FULL,也就是全表扫描,咱们可能会问,这个emp表不是有创建索引吗?其实有索引也没有用,由于咱们就是要提取整个表的数据,索引没有意义,这也说明一个状况,有索引的表,不必定效率就高,后面会讲到。

对于Cost这个指标,这是oracle优化器用来衡量这条sql执行的代价有多大,好比须要消耗多少CPU计算资源呀之类的。

下面介绍几种经过索引访问方式的SQL例子

1、惟一索引(index unique scan)

empnoemp表的主键,是一个惟一索引,咱们执行下面sql语句,查看其执行计划,以下图:

select * from emp where empno=7782

从执行计划中显示的INDEX UNIQUE SCAN能够看出,这句sql,oracle优化器会执行惟一索引扫描,扫描完索引以后,咱们获得索引的值为7782,而后oracle确定要去数据文件中去取这条编号对应的数据块返回嘛,所以咱们能够看到执行计划中显示了TABLE ACCESS BY INDEX ROWID,由于索引存的是每一行的id,所以oracle根据rowid这个属性去找对应的数据。

其实去访问数据块取数据这个步骤有时候是没有的,也就是当你只想取其编号empno而不是*的时候,执行计划就不会去数据文件中取数据了,也就是步骤2不会有了,由于oracle直接从索引扫描到以后就直接返回索引这个值就好了,不必去取数据,咱们又不须要,验证以下:

select empno from emp where empno=7782

其实咱们通常也不会写这样的sql吧,哈哈~~~

2、范围索引扫描(index range scan)

假设咱们执行下面sql语句:

select job from emp where empno>7782

其执行计划以下:

从执行计划中能够看出,这种类型的SQL语句,采用的执行方式为index range scan范围索引扫描。

3、全索引扫描(index full scan)

全索引扫描,顾名思义就是扫描整个索引区域就能肯定出执行结果,好比下面sql语句:

select count(*) from emp

咱们统计整个表的全部数据个数,直接读索引数据块的个数便可,步骤2为将步骤一的记过进行一个求和,汇总获得一个总数返回。

4、全索引快速扫描(index fast full scan)

咱们先用下面语句拷贝一个表并将重命名:

create table emp1 as (select * from emp);
truncate table emp1;
-- 插入1,000,000条数据
BEGIN
FOR I IN 0..1000000 LOOP
INSERT INTO EMP1(EMPNO,ENAME) VALUES(
I,CONCAT('TBL',I));
END LOOP;
END;
复制代码

表建好以后,咱们看下下面sql的执行计划,看看当数据量大的时候,这句sql会不会采用index fast full scan

index fast full scan区别于index full scan的地方是前者能够一次性读取多个数据块,相似于并行,然后者串行读取。

使用这种执行方式的SQL语句通常是那种能够直接经过索引就能肯定出执行结果,好比咱们执行下面SQL:

5、索引跳跃扫描(index skip scan)

index skip scan是oracle 9i以后才提供的索引扫描方式,主要使用来解决组合索引中,where条件使用非前导列查询时,默认采用ACCESS TABLE FULL全表扫描的缺点。可是使用该特性是有一些限制条件的,主要有下面几个点:

  • 一、组合索引前导列惟一值较少(重复值不少)
  • 二、数据库采用CBO优化器,且表和索引都通过分析
  • 三、where查询条件中不存在组合索引前导列

接下来咱们主要验证两个问题:

测试相应的SQL语句以下:

--删除表
drop table student;
commit;

-- 建立Student表
create table STUDENT
(
  stuno    NUMBER(11),
  stuname  VARCHAR2(20),
  schoolno NUMBER(11),
  age      NUMBER(3)
);
commit;

-- 建立组合索引
create index stucombindex on student(stuname,schoolno);
commit;

--F5查看执行计划,会看到是ACCESS TABLE FULL全表扫描,stuname是前导列,schoolno为非前导列
select * from student t where t.schoolno=100;


-- 赋值100万条数据
BEGIN
FOR i IN 0..1000000 LOOP
INSERT INTO student(stuno,stuname,schoolno) VALUES(
i,'TBL',i);
END LOOP;
END;
commit;


-- 更新3条数据的前导列为不一样值
update student t set t.stuname=concat('s',t.stuno) where mod(t.stuno,10000)=0;
commit;

select count(*),count(distinct stuname) from student;

-- 对表、索引进行分析
analyze table student compute statistics for table for all columns for all indexes;

-- 此处查看执行计划,可看到优化器采用的是index skip scan
select * from student where schoolno = 1000;
复制代码

一、组合索引中,使用非前导列进行查询时,优化器采用的是ACCESS TABLE FULL全表扫描

首先将上述sql中,倒数第二句SQL注释掉,将会输出以下内容,默认采用全表扫描:

二、验证index skip scan

通过咱们的验证,咱们能够知道,当咱们创建组合索引时,平常开发中,咱们尽量将 __需求常常用到、选择性高重复值少__的列做为前导列,这样才能最大程度减小非引导列不走索引或者只走跳跃索引的状况。

另外咱们何时创建组合索引呢?主要考虑下面几种状况:

  • 一、当单条件查询时,返回较多数据
  • 二、当符合条件查询时,返回数据较少

当且仅当条件1和条件2同时成立时,咱们这个时候能够创建组合索引了,举个例子,员工表employee中,age=28这个条件的人很是多,role=Java程序员这个条件返回的数据也很是多,可是age=28 and role=Java程序员返回的数据却很是少!

单条件指:where xxx=xxx 复合条件指:where xxx=xxx and xxx1=xxx1

相关文章
相关标签/搜索