利用B*TREE的特性,為列值分佈極端不平衡的表建立特殊索引來提高效率

tianya_2011發表於2012-03-27
    問題提出:一個訂單處理系統,需要頻繁的取出最早提交但是還未處理的訂單來進行處理,隨著時間的累加,表中資料越來越多,效能也越來越低。

   首先來看看錶結構,簡化後如下:
ID NUMBER(10) Y
COMMITDATE DATE Y
STATUS NUMBER(1) Y
   其中ID是主鍵,唯一的標明瞭一個訂單,COMMITDATE表示訂單的提交時間,STATUS表示訂單的處理狀態,其中0表示未處理,1表示已處理。當然此表還有很多列來描述一筆訂單,這裡只是簡化的結構。此表當初建立了tab_order(commitdate desc,status)的索引。那麼查詢最早提交併且未處理的sql的實現如下:
SELECT *
FROM (SELECT A.*, ROWNUM RW
FROM (SELECT *
FROM TAB_ORDER
WHERE STATUS = 0
ORDER BY COMMITDATE DESC) A)
WHERE RW = 1;

--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 460 | 22080 | 49 (0)| 00:00:01 |
|* 1 | VIEW | | 460 | 22080 | 49 (0)| 00:00:01 |
| 2 | COUNT | | | | | |
| 3 | VIEW | | 460 | 16100 | 49 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| TAB_ORDER | 460 | 16100 | 49 (0)| 00:00:01 |
|* 5 | INDEX FULL SCAN | IDX_ORDER | 435 | | 49 (3)| 00:00:01 |
--------------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
325 consistent gets
0 physical reads
0 redo size
716 bytes sent via SQL*Net to client
469 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
    可以看到,首先是對索引進行全掃描,然後得到表中的資料,再進行過濾,得到時間最早的一行。因為前面建立的是基於COMMITDATE的倒序索引,所以把排序的過程省略了。但是當資料量很大的時候,全掃描這個索引的cost也是很大的,而且業務規定不能限定COMMITDATE時間,那麼因為可能由於某種原因導致的很早的訂單一直沒有得到處理。那麼這樣的環境肯定會導致資料越來越多,速度越來越慢。

改進方案:

    考慮到系統中99%以上的訂單都是已經處理掉的,而未處理的訂單永遠是少數,因此可以利用B*TREE中不會包含NULL值的特性,只為status為0的建立索引,這樣索引的大小就永遠和status為0的記錄數相關,只要未處理的訂單不會很多,這個索引就永遠會很小,那麼效率就不會隨著資料的增長而下降。建立索引如下:
create index idx_order on tab_order(decode(status,0,0,null));
改寫sql如下以便讓執行計劃走索引:
SELECT *
FROM (SELECT A.*, ROWNUM RW
FROM (SELECT *
FROM TAB_ORDER
WHERE DECODE(STATUS, 0, 0, NULL) = 0
ORDER BY COMMITDATE DESC) A)
WHERE RW = 1;


執行計劃如下:

---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 460 | 22080 | 2 (50)| 00:00:01 |
|* 1 | VIEW | | 460 | 22080 | 2 (50)| 00:00:01 |
| 2 | COUNT | | | | | |
| 3 | VIEW | | 460 | 16100 | 2 (50)| 00:00:01 |
| 4 | SORT ORDER BY | | 460 | 16100 | 2 (50)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| TAB_ORDER | 460 | 16100 | 1 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | IDX_ORDER | 435 | | 1 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
2 consistent gets
0 physical reads
0 redo size
716 bytes sent via SQL*Net to client
469 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed
  經過對比可以發現,consistent gets已經大大的下降,程式效率得到提升,最關鍵的問題是,程式的效率不會隨著資料量的增加而下降。

     總結:當表中某些列的資料分佈極端不平衡,而符合查詢條件的返回結果又相當少的時候,可以透過DECODE函式轉換和利用B*TREE不儲存NULL值的特性,使得需要使用的索引很小,而且索引大小不隨資料量的變大而變大,從而提高了程式效率。


 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/25373498/viewspace-719648/,如需轉載,請註明出處,否則將追究法律責任。

相關文章