【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

相關文章
相關標籤/搜索