1. 程式人生 > >DB2資料庫查詢過程(Query Processing)----簡單索引訪問(Simple Indexed Access)

DB2資料庫查詢過程(Query Processing)----簡單索引訪問(Simple Indexed Access)

索引對於資料庫的效能有著舉足輕重的作用。上一篇文章已經介紹了沒有索引的情況下表掃描訪問相關知識,本文討論有索引的情況下資料庫系統如何使用索引進行資料訪問,內容會比較複雜,強烈建議參看《深入理解DB2索引(Index)》,理解DB2索引的結構,特別是B+樹後再閱讀本文,否則看起來可能會比較吃力。

由於“基於索引的訪問”內容比較龐雜,現在只准備介紹對於一張表使用索引的情況,不考慮多表連線的問題。分三篇進行介紹,這一篇先介紹簡單索引訪問,下一篇介紹匹配索引掃描和複合索引,最後再介紹一下多索引訪問。

基於索引的訪問不一定是需要進行掃描的,但是為了表述習慣,這裡統稱為索引掃描(Index Scan)。

可索引謂詞和不可索引謂詞(Indexable Predicate and Non-Indexable Predicate)

謂詞(Predicate)

對於SQL語句,Where子句後面的條件表示式稱為謂詞。例如:Select * From EMPLOYEE Where EMPNO=000110 . EMPNO=000110就是謂詞。

如果我們在EMPNO列上建立一個索引,那麼上述SQL查詢就可以使用索引提高查詢效率,EMPNO=000110就是可索引謂詞。

但是考慮SQL語句:Select * From EMPLOYEE Where EMPNO<>000110 .

即便在EMPNO列上建立了索引,進行該SQL查詢時,仍然不能使用該索引,這是因為<>(不等於)操作符無法利用索引B+樹結構的優勢,如果使用了該索引,不僅要將索引頁讀入記憶體進行無意義的查詢工作(查詢不等於的值就等於所有葉結點都要掃描一遍),還需要將幾乎所有的資料頁讀入記憶體(凡是EMPNO不等於000110的所有資料頁都要讀入記憶體)。還不如直接進行表掃描,省了讀入索引頁和掃描索引的開銷。

這種即便使用了索引也無法帶來效率提升的謂詞就稱為不可索引謂詞。

因此,資料庫系統優化器只會對可索引謂詞考慮使用索引掃描,不可索引謂詞即便在相應列上建立了索引也依然使用表掃描。

劃分

下表是常見謂詞是否可索引謂詞的列表:

謂詞型別

Indexable?

Stage 1?

注    釋

COL = value

Y

Y

16

COL = noncol expr

Y

Y

9,11,12,15

COL IS NULL

Y

Y

20,21

COL op value

Y

Y

13

COL op noncol expr

Y

Y

9,11,12,13

COL BETWEEN value1

AND value2

Y

Y

13

COL BETWEEN noncol expr1

AND noncol expr2

Y

Y

9,11,12,13,23

value BETWEEN COL1

AND COL2

N

N

COL BETWEEN COL1

AND COL2

N

N

10

COL BETWEEN expression1

AND expression2

Y

Y

6,7,11,12,13,14

COL LIKE 'pattern'

Y

Y

5

COL IN (list)

Y

Y

17,18

COL <> value

N

Y

8,11

COL <> nocol expr

N

Y

8,11

COL IS NOT NULL

Y

Y

21

COL NOT BETWEEN value1

AND value2

N

Y

謂詞型別

Indexable?

Stage 1?

註釋

COL NOT BETWEEN noncol expr1

AND noncol expr2

N

Y

value NOT BETWEEN COL1

AND COL2

N

N

COL NOT IN (list)

N

Y

COL NOT LIKE ' char'

N

Y

5

COL LIKE '%char'

N

Y

1,5

COL LIKE '_char'

N

Y

1,5

COL LIKE host variable

Y

Y

2,5

T1.COL = T2 col expr

Y

Y

6,9,11,14,15

T1.COL op T2 col expr

Y

Y

6,9,11,12,13,14,15

T1.COL <> T2 col expr

N

Y

8,11

T1.COL1 = T1.COL2

N

N

3

T1.COL1 op T1.COL2

N

N

3

T1.COL1 <> T1.COL2

N

N

3

COL=(nocor subq)

Y

Y

COL = ANY (nocor subq)

N

N

22

COL = ALL (nocor subq)

N

N

COL op (nocor subq)

Y

Y

COL op ANY (nocor subq)

Y

Y

22

COL op ALL (nocor subq)

Y

Y

COL <> (nocor subq)

N

Y

COL <> ANY (nocor subq)

N

N

22

COL <> ALL (nocor subq)

N

N

COL IN (nocor subq)

Y

Y

24

(COL1,…,COLn) IN (nocor subq)

Y

Y

COL NOT IN (nocor subq)

N

N

(COL1,…,COLn) NOT IN (nocor subq)

N

N

COL=(cor subq)

N

N

4

COL = ANY (cor subq)

N

N

22

COL = ALL (cor subq)

N

N

COL op (cor subq)

N

N

4

COL op ANY (cor subq)

N

N

22

謂詞型別

Indexable?

Stage 1?

注    釋

COL op ALL (cor subq)

N

N

COL <> (cor subq)

N

N

4

COL <> ANY (cor subq)

N

N

22

COL <> ALL (cor subq)

N

N

COL IN (cor subq)

N

N

19

 (COL1,…,COLn) IN (cor subq)

N

N

COL NOT IN (cor subq)

N

N

(COL1,…,COLn) NOT IN (cor subq)

N

N

COL IS DISTINCT FROM value

N

Y

8,11

COL IS NOT DISTINCT FROM value

Y

Y

16

COL IS DISTINCT FROM noncol expr

N

Y

8,11

COL IS NOT DISTINCT FROM noncol expr

Y

Y

9,11,12,15

T1.COL1 IS DISTINCT FROM T2.COL2

N

N

3

T1.COL1 IS NOT DISTINCT FROM T1.COL2

N

N

3

T1.COL1 IS DISTINCT FROM T2 col expr

N

Y

8,11

T1.COL1 IS NOT DISTINCT FROM T2 col expr

Y

Y

6,9,11,12,14,15

COL IS DISTINCT FROM (noncor subq)

N

Y

COL IS NOT DISTINCT FROM (noncor subq)

Y

Y

COL IS DISTINCT FROM ANY (noncor subq)

N

N

22

COL IS NOT DISTINCT FROM ANY (noncor subq)

N

N

22

COL IS DISTINCT FROM ALL (noncor subq)

N

N

COL IS NOT DISTINCT FROM ALL (noncor subq)

N

N

4

COL IS DISTINCT FROM (cor subq)

N

N

4

COL IS NOT DISTINCT FROM (cor subq)

N

N

22

COL IS DISTINCT FROM ANY (cor subq)

N

N

22

COL IS NOT DISTINCT FROM ANY (cor subq)

N

N

22

COL IS DISTINCT FROM ALL (cor subq)

N

N

COL IS NOT DISTINCT FROM ALL (cor subq)

N

N

EXISTS (subq)

N

N

19

NOT EXISTS (subq)

N

N

expression = value

N

N

expression <> value

N

N

expression op value

N

N

expression op (subq)

N

N


匹配索引掃描和非匹配索引掃描(Matching Index Scan and Non-Matching Index Scan)

現在假設有一張聯絡人表PHONEBOOK,該表上有LASTNAME、FIRSTNME等若干列,在LASTNAME,FIRSTNME列上建有一個索引:

CREATE INDEX PHONEBOOK_IDX ONPHONEBOOK (LASTNAME,FIRSTNME)                     //為方便解釋,假設這個索引是一個2層索引

注意這個索引是一個複合索引(Composite Index),列的排列順序是非常重要的,第一個列是主索引列,其他列都是從索引列。

該表結構如圖:


匹配索引掃描(Matching Index Scan

如果現在有查詢語句:

Select * From PHONEBOOK Where LASTNAME Like 'S%' .

那麼整個索引掃描過程就是這樣的:


這張圖所表述的意思是:

索引由根結點頁(Root Page)和葉結點頁(Leaf Pages)構成,只有兩層,沒有非葉結點頁。

索引項由兩部分組成:由LASTNAME和FIRSTNME構成的搜尋碼和索引指標(圖中括號內的數字)。

LASTNAME的值中字母排序小於或等於Howell的索引項位於最左側的頁結點,且這些頁索引項按LASTNAME排序,LASTNAME相同的再按FIRSTNME排序;大於Howell且小於或等於Peters的位於中間的頁結點;大於Peters且小於或等於Zidler的位於右側頁結點。(其實這個就是B+樹的規範結構,這裡粗略解釋一下)

紅線以上的是索引頁,紅線以下的是資料頁。資料頁從左到右編號,頁中的資料行從上到下編號。比如:索引項Quinn,Tony(6,2)就表示LASTNAME為Quinn,FIRSTNME為Tony的資料行的位置是:6號資料頁的2號槽位。  (這個就是資料頁內部結構,不理解的話參看:《深入理解資料庫磁碟儲存(Disk Storage)》)

青色線表示掃描路徑,青色線穿過的頁為索引掃描過的頁。SQL語句查詢的是LASTNAME以S開頭的行,所以從根結點頁開始,直接找到右側的索引頁,再由該頁中的滿足條件的索引項的行指標找到資料頁的位置。注意:整個掃描過程中沒有經過左側和中間的葉結點頁。

這種使用索引並且索引中的主搜尋碼可用的索引掃描方式就稱為匹配索引掃描。可用的索引謂詞就稱為匹配謂詞(Matching Predicate)。

當然了,對於非複合索引,即單列索引,如果該列上建有索引且SQL語句的Where子句後的條件謂詞是可索引謂詞,那麼該查詢的訪問方式自然也是匹配索引掃描。

比如表T1的C1列上有索引C1X,則SQL查詢: Select * From T1 Where C1=10 的訪問方式是匹配索引掃描。

非匹配索引掃描(Non-Matching Index Scan

現在考慮查詢:

Select * From PHONEBOOK Where FIRSTNME = ‘Abe’

這個SQL語句的條件謂詞是FIRSTNME = ‘Abe’,但是索引的主搜尋碼是LASTNAME。也就是說,索引的B+樹結構是根據LASTNAME來構建的,Where子句後面如果沒有LASTNAME的條件謂詞,就不可能像上面匹配索引掃描一樣,直接找到滿足條件的索引頁。那麼,如果要使用這個索引,就只能遍歷所有的葉結點索引頁才能找到所有滿足條件的索引項,獲取相應的資料頁指標。其掃描過程是這樣的:


這個圖就不再細說了,對比著上圖就能看懂了。需要注意的是:因為無法使用主搜尋碼,掃描時就沒有訪問根結點頁,直接遍歷了所有的葉結點頁(B+樹的特點就是所有的搜尋碼都存在於葉結點頁中)。

這種使用了索引但因為索引的主搜尋碼不可用(SQL語句中條件謂詞不含主搜尋碼或含主搜尋碼的條件謂詞是不可索引謂詞)而必須逐個掃描索引葉結點頁的索引掃描方式稱為非匹配索引掃描。

疑問

這裡就有一個疑問了:

既然非匹配索引掃描需要遍歷索引的全部葉結點頁,同樣是遍歷:表掃描直接遍歷資料頁,非匹配索引掃描則是遍歷索引頁然後找到資料頁。那麼這種情況為什麼不直接進行表掃描呢?

我們知道,資料頁和索引頁是相同大小的頁。資料頁中的一條記錄是一個數據行,索引頁中的一條記錄是一個索引項(搜尋碼+指標)。很顯然,一個數據行通常會比一個索引項大很多,也就是說,一個相同大小的頁,索引頁中能容納的記錄數要遠大於資料頁中能容納的記錄數。假設一個數據頁能容納10行資料,一個索引頁能容納100個索引項,表中有10000行資料,那麼需要的資料頁為10000/10=1000頁。而索引頁只需10000/100=100頁(由於可能存在搜尋碼重複的行以及DB2的索引壓縮技術,實際需要的索引頁會更少)。

如果使用非匹配索引掃描,只需要將100個索引頁調入緩衝池逐個掃描然後將滿足條件的資料頁讀入緩衝池,假設滿足條件的資料頁有10頁,則總共需要的I/O次數為:

100+10=110次。

而如果使用表掃描,則需要將1000個數據頁讀入緩衝池然後逐個掃描,則總共需要的I/O次數為:1000次。

很顯然,1000 >>110,另外,由於DB2採用的特殊演算法,系統對索引頁的掃描速度是遠快於對資料頁的掃描速度的。所以當然是使用非匹配索引掃描更好了。

那麼既然掃描索引頁更快且需要讀入緩衝池的頁面更少,為什麼對於有索引,但條件謂詞是不可索引謂詞的情況卻不使用索引掃描呢?

原因其實開始已經解釋過了,不可索引謂詞的特點是滿足條件的行比不滿足條件的行多得多,比如"<>(不等於)"這個條件,大部分頁都是滿足條件的,這就導致大量的滿足條件的資料頁需要被讀入緩衝池。還是考慮上面的例子,如果使用非匹配索引掃描,則不僅要讀100個索引頁到緩衝池,還要讀990個數據頁,總共的I/O次數為100+990=1090次,大於直接使用表掃描的1000次。當然要選擇表掃描了。

事實上,滿足非匹配索引掃描條件的情況也不一定都是使用非匹配索引掃描的,因為有的情況下,表掃描的I/O開銷仍然會優於非匹配索引掃描的I/O開銷。而且成本估算也不單單考量I/O開銷,優化器綜合各種統計資訊最終選擇的最優訪問計劃很可能是表掃描而不是非匹配索引掃描。

直接索引查詢

直接索引查詢是一種比較特殊的索引查詢方式。其意思是:如果SQL語句的Where子句後面的謂詞包含了對索引中全部的搜尋碼的條件判斷,那麼就不需要對索引頁進行掃描就可以直接定位了。例如表T1(C1,C2,C3,C4,C5)上建立索引CX(C1,C2,C3),現在有SQL查詢:

Select * From T1 Where C1=10 And C2=5 And C3=3

索引上三個搜尋碼都在查詢語句中給出了明確的條件,所以可以直接定位到相應索引頁。

但是如果SQL查詢語句是這樣的:

Select * From T1 Where C1=10 And C2=5

那麼由於C3未指定,滿足C1和C2條件的索引項可能位於不同的索引頁上(比如索引頁A上有索引項[C1=10,C2=5,C3=2],索引頁B上有索引項[C1=10,C2=5,C3=3],這兩個索引項都是滿足條件的,但位於不同的索引頁),這就需要在找到第一個滿足條件的索引頁後對後面的索引頁也要進行遍歷才能保證不漏掉滿足條件的索引項。可想而知,這樣的索引結構只有所有行在C1和C2列上有大量重複值的情況下才出現。

第一種情況就是直接索引查詢,第二種情況就是匹配索引掃描了。我們仍然可以認為直接索引查詢就是匹配索引掃描的一種特例。就不進行單獨討論了。

只掃描索引(Index-Only Access)

這種索引掃描方式是所有索引掃描中效率最高的,因為它只需要掃描索引頁,而無需將資料頁讀入緩衝池。

出現這種掃描的可能情況有兩種:

A.索引是一個包含索引(要求索引是唯一索引),即索引包含了額外的列值,而SQL查詢語句所查詢的就是這個列值,那麼直接在索引項中就能讀取到所需的列值,自然不必去讀對應的資料頁了。

例如索引:

CREATE UNIQUE INDEX CX ON T1 ( C1 ) INCLUDE ( C2 ) 

SQL語句:

Select C2 From T1 Where C1=10

直接從索引頁讀取C2值。

B.要查詢的列就在索引的搜尋碼中。

例如索引:

CREATE UNIQUE INDEX CX ON T1 ( C1,C2 )

SQL語句:

Select C2 From T1 Where C1=10   

同樣直接從索引頁就能夠讀取C2值。

其實這兩種情況本質上是一樣的。下面也給一個只掃描索引的示意圖:


注:以下討論的索引掃描都是匹配索引掃描

唯一索引掃描和非唯一索引掃描(Unique Index Scan and Non-Unique Index Scan )

唯一索引是指表中所有的資料行在索引列值上的值都是唯一的,不存在重複值。對於使用唯一索引和非唯一索引進行資料訪問的掃描方式實際上區別不大,最主要的區別是二者的索引結構可能會有區別,這會導致進行索引掃描的I/O開銷的不同(不過掃描索引的I/O開銷通常是微不足道的),至於取資料頁的開銷就沒有討論的必要了,唯一索引掃描最終只需要將一個數據頁讀入記憶體,而非唯一索引掃描可能需要取若干頁。下面以一個具體的例子解釋一下唯一索引掃描和非唯一索引掃描。

有一張統計各手機號段的歸屬資訊的表MOBILE,該表有ID、MOBILENUM、PROVINCE、CITY、MOBILETYPE、POSTCODE、AREACODE這7個列,表中有50000000行資料。表中一行資料的長度是400個位元組(400B),其中MOBILENUM列長度為36B,CITY列長度為8B。表儲存在4KB頁的表空間中,假設為MOBILE表提供的所有資料頁和索引頁的PCTFREE都是0(即所有頁空間都用於資料儲存)。

一個數據行長度為400B,則一個4KB資料頁能容納4KB/400B=10行。容納全部50000000行資料需要50000000/10=5000000頁資料頁。

現在在表Mobile上建立一個MOBILENUM列上的唯一索引NUM_IDX和一個在CITY列上的普通索引CITY_IDX。

先來考慮唯一索引NUM_IDX的結構

由於一個索引項由搜尋碼+邏輯指標(RID,行指示器)構成,NUM_IDX的搜尋碼MOBILENUM長度為36B,DB2中一個邏輯指標(RID)的長度為4B(這是常規表空間的RID,新版大型表空間的RID為6B),則一個索引項大小為36B+4B=40B;

一個索引頁大小為4KB,除去資料頁頭部的空間佔用,可用於儲存索引項的空間為4000B,則一個索引頁能夠儲存的索引項個數為4000B/40B=100個;

由於該索引是唯一索引,所以值不同的搜尋碼的個數與表中行的個數相同,為50000000行,即索引項的個數為50000000行。因此總共需要的葉結點索引頁數為50000000/100=500000頁;

一個索引頁能夠儲存的索引項個數為100個,則根據B+樹的特點,葉結點的上層非葉結點的個數最多為500000/100=5000頁;

同理,該非葉結點的上層非葉結點的個數為最多為5000/100=50頁。

50<100,因此該層非葉結點的上層為根結點。如此,該唯一索引的B+樹為4層結構:根結點+2層非葉結點+頁結點。

現在考慮該索引結構的開銷:

對於SQL查詢語句:

Select * From MOBILE Where MOBILENUM=1500000

很明顯,需要掃描索引,首先向緩衝池讀入根結點頁,根據根結點讀入滿足條件的非葉結點頁,然後再次讀入下一級非葉結點頁,最後讀入符合條件的葉結點頁。總共需要4次隨機I/O;

然後根據葉結點頁中的RID找到相應的資料頁,讀入該資料頁,進行1次隨機I/O;

因此,該查詢總的I/O開銷為:4次隨機I/O+1次隨機I/O。

再來考慮普通索引CITY_IDX的結構

由於CITY列上會有重複值,我們不妨假設所有的資料行中,不同的CITY值一共有100個,這就相當於每一個CITY值有50000000/100=500000個重複行。因此,對於索引中的一個搜尋碼值,可能對應500000個左右的邏輯指標(RID)。

由於DB2對於有重複值的搜尋碼採用的高效索引壓縮技術,一個邏輯塊中儲存一個搜尋碼+N個邏輯指標(RID),這N個邏輯指標指向擁有該搜尋碼的N個不同的資料頁。

由於這種邏輯塊結構與先前討論的索引項結構是完全不同的,所以只能用一種新的角度來討論這種情況下的索引項了。(關於DB2索引邏輯塊結構可以參看:《深入理解DB2索引(Index)》)

由於一個邏輯塊最多可以包含255個RID,一個RID大小為4B,因此一個邏輯塊大小為2B(邏輯塊字首的大小)+8B(CITY列的長度)+255*4B=1030B,為方便計算,以1000B計;

那麼一個4KB頁能容納的邏輯塊個數為4KB/1000B=4個。由於一個邏輯塊中RID的個數為255個,因此一個4KB索引頁中容納了約1000個RID;

對於無重複搜尋碼值的索引而言,一個搜尋碼對應一個RID,即一個索引項對應一個RID,一個RID又對應一個數據行。那麼,對於有重複搜尋碼值的情況,同樣是一個RID對應了一個數據行,如果我們還是認為一個索引項由一個搜尋碼和一個RID構成的話,那麼可以說:一個索引頁中包含1000個索引項(因為一個索引頁中有1000個RID呀,只是搜尋碼被壓縮了,若干個RID共用了一個搜尋碼而已);

這個應該是可以理解的。但是要區分清楚,對於有重複搜尋碼值的索引,頁結點索引頁實際上是由若干個邏輯塊組成的(一個邏輯塊就是一個頁中的一條記錄),其效果等同於一個索引頁包含了1000個索引項而已。畢竟,一個4KB頁能容納的記錄數最大為255個(槽號只有1個位元組大小),是不可能容納1000條記錄的;

還有一點需要注意:這種索引壓縮技術僅僅應用於葉結點的索引頁,因為非頁結點和根結點的結構依然是“一個頁對應若干個索引項,一個索引項對應一個RID”的結構,無需進行壓縮。這一點應該也是可以理解的;

理解了索引壓縮後,我們就可以繼續按照之前的討論方式進行計算了。

上面說了,一個葉結點索引頁包含1000個索引項,那麼對於50000000行資料,所需要的葉結點頁的個數為50000000/1000=50000頁;

由於非葉結點頁沒有使用壓縮技術,索引項結構依然是搜尋碼長度+RID長度。則一個非葉結點頁能容納的索引項個數為4KB/(4B+8B)=333個,那麼50000個葉結點頁需要的上層非葉結點頁個數最多為50000/333=151頁;

151<333,故該非葉結點層的上層為根結點。如此,該普通索引的B+樹結構為3層結構:根結點+1層非葉結點+葉結點;

現在考慮該索引結構的開銷:

對於SQL查詢語句:

Select * From MOBILE Where CITY='武漢'

很明顯,需要掃描索引,首先向緩衝池讀入根結點頁,根據根結點讀入滿足條件的非葉結點頁。這就需要2次隨機I/O;

由於不同的CITY值一共有100個,總的資料行數為50000000行,那麼我們可以認為CITY='武漢'的行數為500000行。由於一個葉結點頁包含1000個RID,一個數據行對應一個RID,因此,至少要讀入500000/1000=500個葉結點頁。由於葉結點索引頁通常是連續儲存在磁碟上的,所以對於葉結點索引頁的讀取可以使用順序預取I/O,即需要500次順序預取I/O。

由於CITY='武漢'的各個資料行可能散落於各個資料頁中,因此最多需要讀入緩衝池的資料頁為500000頁,即進行500000次I/O,由於資料頁不一定是連續的,因此最壞情況下,使用隨機I/O進行這500000次讀取。

因此,該查詢總的I/O開銷為:2次隨機I/O+500次順序預取I/O+500000次隨機I/O。

其實,唯一索引掃描和非唯一索引掃描比較的意義並不大,因為通常,唯一索引掃描都會快於非唯一索引掃描,畢竟由於滿足條件的行是唯一的,唯一索引掃描無論是在取索引階段還是在取資料階段,都只需要取一頁。而非唯一索引掃描由於滿足條件的行通常是不唯一的,取索引和取資料都需要取若干行。

之所以合起來介紹,只是想介紹一下索引頁內部結構不同的兩種索引掃描方式的I/O開銷計算方法。

另外,在實際的資料庫系統中,索引的根結點頁和非葉結點頁通常是常駐緩衝池的(因為頻繁使用),它們無需花費I/O開銷。

聚簇匹配索引掃描和非聚簇匹配索引掃描(Clustered Matching Index Scan and Non-Clustered Matching Index Scan

這兩種索引掃描方式其實沒有什麼區別,唯一的不同在於,對於聚簇匹配索引掃描,在進行資料頁的讀取的過程中,由於資料頁是有序儲存的,可以使用I/O順序預取。而對於非聚簇匹配索引掃描,由於資料頁通常是亂序的,所以只能使用I/O列表預取或者隨機I/O。兩種方式的效率自然是不能相提並論了。

不過要說明的是,對於非聚簇匹配索引掃描,並不是一定不能使用I/O順序預取。如果優化器探知其資料頁也是有序存放的(只是沒有聲稱自己是聚簇的而已),同樣可以使用I/O順序預取。

比較簡單,就不細說了。