1. 程式人生 > >提高DB2查詢效能的常用方法(二)

提高DB2查詢效能的常用方法(二)

下面我們將從三個方面介紹一些提高查詢效能的方法。

  建立索引

  根據查詢所使用的列建立多列索引

  建立索引是用來提高查詢效能最常用的方法。對於一個特定的查詢,可以為某一個表所有出現在查詢中的列建立一個聯合索引,包括出現在 select 子句和條件語句中的列。但簡單的建立一個覆蓋所有列的索引並不一定能有效提高查詢,因為在多列索引中列的順序是非常重要的。這個特性是由於索引的 B+ 樹結構決定的。一般情況下,要根據謂詞的選擇度來排列索引中各列的位置,選擇度大的謂詞所使用的列放在索引的,把那些只存在與 select 子句中的列放在索引的最後。譬如清單 5 中的查詢:


  清單5. 索引中的謂詞位置

        


  select add_date
from temp.customer
where city = 'WASHINGTON'
and cntry_code = 'USA';

  對於這樣的查詢可以在 temp.customer 上建立 (city,cntry_code,add_date) 索引。由於該索引包含了 temp.customer 所有用到的列,此查詢將不會訪問 temp.customer 的資料頁面,而直接使用了索引頁面。對於包含多列的聯合索引,索引樹中的根節點和中間節點儲存了多列的值的聯合。這就決定了存在兩種索引掃描。回到清單 5 中的查詢,由於此查詢在新建索引的第一列上存在謂詞條件,DB2 能夠根據這個謂詞條件從索引樹的根節點開始遍歷,經過中間節點最後定位到某一個葉子節點,然後從此葉子節點開始往後進行在葉子節點上的索引掃描,直到找到所有滿足條件的記錄。這種索引掃描稱之為 Matching Index Scan。但是如果將 add_date 放在索引的第一個位置,而查詢並不存在 add_date 上的謂詞條件,那麼這個索引掃描將會從第一個索引葉子節點開始,它無法從根節點開始並經過中間節點直接定位到某一個葉子節點,這種掃描的範圍擴大到了整個索引,我們稱之為 Non-matching Index Scan。圖 5 顯示了 DB2 根據不同索引生成的存取計劃。


  圖5. 根據不同索引生成的存取計劃

  根據不同索引生成的存取計劃

  根據不同索引生成的存取計劃

  根據條件語句中的謂詞的選擇度建立索引

  因為建立索引需要佔用資料庫的儲存空間,所以需要在空間和時間效能之間進行權衡。很多時候,只考慮那些在條件子句中有條件判斷的列上建立索引會也會同樣有效,同時節約了空間。譬如清單 5 中的查詢,可以只建立 (city,cntry_code) 索引。我們還可以進一步地檢查條件語句中的這兩個謂詞的選擇度,執行清單 6 中的語句檢查謂詞選擇度:

  清單 6.檢查謂詞選擇度                
             


   Queries:
1. select count(*) from temp.customer
where city = 'WASHINGTON'
and cntry_code = 'USA';
2. select count(*) from temp.customer
where city = 'WASHINGTON';
3. select count(*) from temp.customer
where cntry_code = 'USA';
Results:
1. 1404
2. 1407
3. 128700


  選擇度越大,過濾掉的記錄越多,返回的結果集也就越小。從清單 6 的結果可以看到,第二個查詢的選擇度幾乎有和整個條件語句相同。因此可以直接建立單列索引 (city),其效能與索引 (city,cntry_code,add_date) 具有相差不多的效能。表 1 中對兩個索引的效能和大小進行了對比。


  表 1. 兩個索引的效能和大小對比

    
索引                 查詢計劃總代價   索引大小
cust_i1(city,cntry_code,add_date) 28.94 timerons   19.52M
cust_i3(city)           63.29 timerons    5.48M

  從表 1 中可以看到單列索引 (city) 具有更加有效的效能空間比,也就是說佔有儘可能小的空間得到儘可能高的查詢速度。

  避免在建有索引的列上使用函式

  這是一個很簡單的原則,如果在建有索引的列上使用函式,由於函式的單調性不確定,函式的返回值和輸入值可能不會一一對應,就可能存在索引中位置差異很大的多個列值可以滿足帶有函式的謂詞條件,因此 DB2 優化器將無法進行 Matching Index Scan,更壞的情況下可能會導致直接進行表掃描。圖 6 中對比了使用 function 前後的存取計劃的變化。

  使用function前後的存取計劃的變化

  
  圖6. 使用 function 前後的存取計劃的變化
 

  那些需要被排序的列上建立索引

  這裡的排序不僅僅指 order by 子句,還包括 distinct 和 group by 子句,他們都會產生排序的操作。由於索引本身是有序的,在其建立過程中已經進行了排序處理,因此在應用這些語句的列上建立索引會降低排序操作的代價。這種情況一般針對於沒有條件語句的查詢。如果存在條件語句,DB2 優化器會首先選擇出滿足條件的紀錄,然後才對中間結果集進行排序。對於沒有條件語句的查詢,排序操作在總的查詢代價中會佔有較大比重,因此能夠較大限度的利用索引的排序結構進行查詢優化。此時可以建立單列索引,如果需要建立聯合索引則需要把被排序的列放在聯合索引的第一列。圖 7 對比了清單 7 中的查詢在建立索引前後的存取計劃。


  清單7. 查詢在建立索引前後的存取計劃

  select distinct add_date from temp.customer;

  在建立索引前後的存取計劃


  圖7. 在建立索引前後的存取計劃

  在建立索引前後的存取計劃
 
  從圖 7 中我們可以看到在沒有索引的情況下 SORT 操作是 24751.69 timerons,但是有索引的情況下,不再需要對結果集進行排序,可以直接進行 UNIQUE 操作,表中顯示了這一操作只花費了 2499.98 timerons.

  圖8對比了清單 8 中的查詢在建立聯合索引前後的存取計劃,從中可以更好的理解索引對排序操作的優化。

  清單8. 查詢示例


               
select cust_name from temp.customer order by add_date;

  建立聯合索引前後的存取計劃


  圖8. 建立聯合索引前後的存取計劃

  索引的 B+ 樹結構決定了索引 temp.cust_i5 的所有葉子節點本身就是按照 add_date 排序的,所以對於清單 8 中的查詢,只需要順序掃描索引 temp.cust_i5 的所有葉子節點。但是對於 temp.cust_i6 索引,其所有葉子節點是按照 cust_name 排序,因此在經過對索引的葉子節點掃描獲得所有資料之後,還需要對 add_date 進行排序操作。

  合理使用include關鍵詞建立索引

  對於類似下面的查詢 :

  清單9. 查詢示例               


select cust_name from temp.customer
where cust_num between '0007000000' and '0007200000'

  在第一點中我們提到可以在 cust_num 和 cust_name 上建立聯合索引來提高查詢效能。但是由於 cust_num 是主鍵,可以使用 include 關鍵字建立唯一性索引:


create unique index temp.cust_i7 on temp.customer(cust_num) include (cust_name)

  使用 include 後,cust_name 列的資料將只存在於索引樹的葉子節點,並不存在於索引的關鍵字中。這種情況下,使用帶有 include 列的唯一索引會帶來優於聯合索引的效能,因為唯一索引能夠避免一些不必要的操作,如排序。對於清單 9 中的查詢建立索引 temp.cust_i7 後存取計劃的代價為 12338.7 timerons,建立聯合索引 temp.cust_i8(cust_num,cust_name) 後的代價為 12363.17 timerons。一般情況下當查詢的 where 子句中存在主鍵的謂詞我們就可以建立帶有 include 列的唯一索引,形成純索引訪問來提高查詢效能。注意 include 只能用在建立唯一性索引中。

  指定索引的排序屬性

  對於下面用來顯示最近一個員工入職的時間的查詢:


select max(add_date) from temp.employee

  很顯然這個查詢會進行全表掃描。查詢計劃如圖 9.a:

  圖 9. 查詢計劃

  查詢計劃

  顯然我們可以在 add_date 上建立索引。根據下面的命令建立索引後的查詢計劃如圖 9.b。

create index temp.employee_i1 on temp.employee(add_date)

  這裡存在一個誤區,大家可能認為既然查詢裡要取得的是 add_date 的最大值,而我們又在 add_date 上建立了一個索引,優化器應該知道從索引樹中直接去尋找最大值。但是實際情況並非如此,因為建立索引的時候並沒有指定排序屬性,預設為 ASC 升序排列,DB2 將會掃描整個索引樹的葉子節點取得所有值後,然後取其最大。我們可以通過設定索引的排序屬性來提高查詢效能,根據下面的命令建立索引後的查詢計劃如圖 9.c。


create index temp.employee_i1 on temp.employee(add_date desc)

  對於降序排列的索引,DB2 不需要掃描整個索引數的葉子節點,因為第一個節點便是最大的。我們同樣可以使用 ALLOW REVERSE SCANS 來指定索引為雙向掃描具有和 DESC 近似的查詢效能。ALLOW REVERSE SCANS 可以被認為是 ASC 和 DESC 的組合,只是在以後資料更新的時候維護成本會相對高一些。

  如果無法改變索引的排序屬性,但是我們具有額外的資訊,該公司每個月都會有新員工入職,那麼這個查詢就可以改寫成:


select max(add_date) from temp.employee where add_date > current timestamp - 1 month

  這樣通過限定一個查詢範圍也會有效地提高查詢效能。