一條sql的好壞,主要來源兩個方面:程序員
咱們以oracle 11g爲例子進行分析。sql
【沒有索引】數據庫
若是一張表沒有創建索引,那麼優化器採用的數據訪問方式也會大相徑庭,這就取決於oracle的數據訪問方式,下邊列舉兩種:bash
【有索引】oracle
創建索引的狀況,也會有不一樣的數據訪問方式,主要有下面5種:oop
上面列舉了幾種數據的訪問方式,其實像咱們平常開發中使用到的排序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
(合併排序鏈接)該連接方式大概的原理就是,判斷原表是否排序,若是未排序,則針對關聯字段進行排序;判斷關聯表是否排序,若是未排序,則進行排序,最後將兩個排序的表進行合併。
咱們要知道oracle數據是如何數據訪問和數據處理的,咱們就要看下執行計劃,但執行計劃又僅僅告訴咱們這些信息。
咱們登錄上sqlplus,就拿一條最簡單的sql進行說明,簡單說下咱們應該如何看懂執行計劃:
select * from emp;
emp
表是oracle自帶的員工表,右鍵點擊Explain Plan
,或者按下F5
查看執行計劃,以下圖:
執行計劃最左邊的Description
列是比較重要的,它會列出這個sql的一些執行步驟,該列有下面幾個查看規則:
上圖告訴咱們,這條語句採用的數據訪問方式是:TABLE ACCESS FULL
,也就是全表掃描,咱們可能會問,這個emp
表不是有創建索引嗎?其實有索引也沒有用,由於咱們就是要提取整個表的數據,索引沒有意義,這也說明一個狀況,有索引的表,不必定效率就高,後面會講到。
對於Cost
這個指標,這是oracle優化器用來衡量這條sql執行的代價有多大,好比須要消耗多少CPU計算資源呀之類的。
下面介紹幾種經過索引訪問方式的SQL例子
empno
是emp
表的主鍵,是一個惟一索引,咱們執行下面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吧,哈哈~~~
假設咱們執行下面sql語句:
select job from emp where empno>7782
其執行計劃以下:
從執行計劃中能夠看出,這種類型的SQL語句,採用的執行方式爲index range scan
範圍索引掃描。
全索引掃描,顧名思義就是掃描整個索引區域就能肯定出執行結果,好比下面sql語句:
select count(*) from emp
咱們統計整個表的全部數據個數,直接讀索引數據塊的個數便可,步驟2爲將步驟一的記過進行一個求和,彙總獲得一個總數返回。
咱們先用下面語句拷貝一個表並將重命名:
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:
index skip scan
是oracle 9i以後才提供的索引掃描方式,主要使用來解決組合索引中,where條件使用非前導列查詢時,默認採用ACCESS TABLE FULL
全表掃描的缺點。可是使用該特性是有一些限制條件的,主要有下面幾個點:
接下來咱們主要驗證兩個問題:
測試相應的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