Hash分割槽表的使用及擴充套件

talio發表於2014-01-23

Hash分割槽是Oracle實現表分割槽的三種基本分割槽方式之一。

對於那些無法有效劃分分割槽範圍的大表,或者出於某些特殊考慮的設計,需要使用Hash分割槽。Hash分割槽是透過對分割槽鍵運用Hash演算法從而決定資料的分割槽歸屬。

使用Hash分割槽有什麼優點呢?

常用的分割槽表所具有的優點:如提高資料可用行,減少管理負擔,改善語句效能等優點,hash分割槽同樣擁有。此外,由於Hash分割槽表是按分割槽鍵的hash計算結果來決定其分割槽的,而特定的分割槽鍵其hash值是固定的,也就是說Hash分割槽表的資料是按分割槽鍵值來聚集的,同樣的分割槽鍵肯定在同一分割槽。

比如,在證券行業,我們經常查詢某一隻股票的K線,

假設表的結構如下:

create table equity

(

id number,

trade_date date,

……);

Equity表可能會很大,對equity表的查詢通常都是指定id,查詢某一交易日期或者某段時期內的其他資訊。這種情況下我們需要如何為equity表選擇分割槽呢?

單從表本身結構來看,似乎trade_date列很適合被選擇用來作範圍分割槽。但如果我們這樣分割槽的話,前面需求中的查詢:指定某一id,查詢其某一範圍內的交易資訊,比如看1年內的K線,則這種查詢常常需要跨分割槽。我們知道,對分割槽表作跨分割槽查詢,很多時候其效能並不會太好,特別是這種查詢很可能還要跨很多分割槽。

你也可能會說,我們再在id, trade_date列上建個索引不就行了,仔細想想是不是這樣呢?這時候的equity表中的資料是按trade_date值來聚集的,同樣trade_date值的資料常常在一個資料塊中,這樣前面需求中所描述的查詢即使透過索引訪問,最終讀表時也常常是去讀離散的資料塊,即每一條記錄需要對應讀一個表資料塊。

如果建成Hash分割槽表,則資料按hash分割槽鍵聚集,就更適合需求中描述的查詢,因為同樣id的記錄必定在同一分割槽,同時,同樣 id值的記錄落在同一資料塊的機率也增大了,從而“一定程度上”減少了IO。

上面對hash分割槽減少IO的描述加了引號,因為僅依靠Hash分割槽表試圖實現大範圍減少IO操作是不現實的,特別是當equity表中記錄的股票數非常多時,同一股票發生在不同交易日的記錄在物理上也很難聚集到相同資料塊中。實際上,如果我們在Hash分割槽的基礎上再對equity表採用IOT表的組織方式,則前面描述的查詢效能就可大為提高。IOT表不在該文討論的範圍之內,這裡就不作進一步討論了。

當我們決定使用Hash表之前,我們還需要確定我們的所選擇的分割槽鍵值是連續分佈的,或者接近連續分割槽,此外,分割槽的個數需要是2的整數冪,比如2,4,8… 這些要求是由Hash函式的特點決定的,這樣我們分割槽表的各個分割槽所包含的資料量才會比較平均。

Hash分割槽表的擴充套件:

Hash分割槽表是透過add partition命令來增加分割槽的。Oracle推薦分割槽的個數是2的冪,比如,2,4,8..等等,這樣可以確保資料在各個分割槽中分佈比較均勻。當然,如前所述,還需要分割槽鍵值是連續分佈的,或接近連續分佈。

增加新分割槽時,需要將一些原有的資料從舊的分割槽劃分到新的分割槽中,那麼這種資料劃分時來源分割槽選擇遵循什麼原則呢?Yangtingkun已經對這方面的知識點作過研究,參考以下link:

要點如下:如果要增加的分割槽是第N個分割槽,大於等於N的最小2的整數冪為M,則當增加第N個分割槽時,這個分割槽的資料來源於分割槽N-M/2。

比如,現在有個Hash分割槽表共有100個分割槽,我們想為其增加一個分割槽,則它是101個分割槽,即上面公式中的N為101,而大於101的最小2的整數冪為128,則M為128,於是,這個101分割槽的資料來源就應該是101-128/2=37分割槽。

換個角度來說,當我們在增加第101分割槽的時候,是需要鎖定37分割槽的,因為我們需要將該分割槽中的部分資料插入到新的101分割槽中。

下面,我們用一個例項來驗證上面的說法,同時看看在實際操作中有什麼需要注意的事項:

Commodity表是我們系統中的一個大表,幾年前在為該表建立Hash分割槽表時,當時的DBA在選擇分割槽數時指定了100個分割槽:

  1. select TABLE_NAME,PARTITION_POSITION,PARTITION_NAME,NUM_ROWS from user_tab_partitions where table_name=\'COMMODITY\' order by PARTITION_POSITION;
  2. TABLE_NAME PARTITION_POSITION PARTITION_NAME NUM_ROWS
  3. -------------- ------------------ ---------------------- ----------
  4. COMMODITY 1 COT_IND01_P1 4405650
  5. COMMODITY 2 COT_IND01_P2 5046650
  6. COMMODITY 3 COT_IND01_P3 5107550
  7. ……
  8. COMMODITY 36 COT_IND01_P36 5718800
  9. COMMODITY 37 COT_IND01_P37 9905200
  10. COMMODITY 38 COT_IND01_P38 10118400
  11. COMMODITY 39 COT_IND01_P39 10404950
  12. COMMODITY 40 COT_IND01_P40 9730850
  13. COMMODITY 41 COT_IND01_P41 9457300
  14. COMMODITY 42 COT_IND01_P42 9717950
  15. COMMODITY 43 COT_IND01_P43 9643900
  16. COMMODITY 44 COT_IND01_P44 11138000
  17. COMMODITY 45 COT_IND01_P45 9381300
  18. COMMODITY 46 COT_IND01_P46 10101150
  19. COMMODITY 47 COT_IND01_P47 8809950
  20. COMMODITY 48 COT_IND01_P48 10611050
  21. COMMODITY 49 COT_IND01_P49 10010600
  22. COMMODITY 50 COT_IND01_P50 8252600
  23. COMMODITY 51 COT_IND01_P51 9709900
  24. COMMODITY 52 COT_IND01_P52 8983200
  25. COMMODITY 53 COT_IND01_P53 9012750
  26. COMMODITY 54 COT_IND01_P54 9310650
  27. COMMODITY 55 COT_IND01_P55 8966450
  28. COMMODITY 56 COT_IND01_P56 8832650
  29. COMMODITY 57 COT_IND01_P57 9470600
  30. COMMODITY 58 COT_IND01_P58 8932450
  31. COMMODITY 59 COT_IND01_P59 9994850
  32. COMMODITY 60 COT_IND01_P60 9617450
  33. COMMODITY 61 COT_IND01_P61 10278850
  34. COMMODITY 62 COT_IND01_P62 9277600
  35. COMMODITY 63 COT_IND01_P63 8136300
  36. COMMODITY 64 COT_IND01_P64 10064600
  37. COMMODITY 65 COT_IND01_P65 3710900
  38. ……
  39. COMMODITY 99 COT_IND01_P99 5273800
  40. COMMODITY 100 COT_IND01_P100 5293350
  41. 100 rows selected.

查詢各個分割槽的資料分佈,我們可以看到,從分割槽37 ~ 64的28個分割槽的記錄數大概是其他分割槽的兩倍。由於100不是2的整數冪,所以Oracle的hash函式是無法保證資料是平均分佈的。我們為該表新增一個新的分割槽COT_IND01_P101:

  1. alter table nts_commodity_ts add partition COT_IND01_P101;
  2. Table altered.
  3. Elapsed: 00:06:58.52

收集統計資訊後查詢新的分割槽記錄數:

  1. select TABLE_NAME,PARTITION_POSITION,PARTITION_NAME,NUM_ROWS from user_tab_partitions where table_name=\'COMMODITY\' and partition_name in (\'COT_IOT_IND01_P37\',\'COT_IOT_IND01_P101\');
  2.  
  3. TABLE_NAME PARTITION_POSITION PARTITION_NAME NUM_ROWS
  4. ------------------ ------------------ --------------------- ----------
  5. COMMODITY 37 COT__IND01_P37 4905200
  6. COMMODITY 101 COT_IND01_P101 5107550

這時,我們可以看到,分割槽37中的資料被接近於平分到了分割槽37和101中。

監控增加分割槽過程中session鎖的情況,我們發現期間有兩個物件被以exclusive模式鎖定了:

  1. SQL> select * from v$lock where sid=1239 and type=\'TM\' and LMODE=6 order by sid,lmode;
  2. ADDR                KADDR          SID TY ID1    ID2 LMODE REQUEST CTIME BLOCK
  3. ---------------- ---------------- ---------- -- ---------- ---------- ---------- ---------- ---------- ----------
  4. FFFFFFFF7D764828 FFFFFFFF7D764888 1239 TM 4004126 0  6 0 72 2
  5. FFFFFFFF7D764828 FFFFFFFF7D764888 1239 TM 4004063 0  6 0 72 2

它們分別是什麼物件呢?

  1. select OBJECT_NAME,SUBOBJECT_NAME,OBJECT_ID from user_objects where object_id in (4004126,4004063)
  2. OBJECT_NAME SUBOBJECT_NAME OBJECT_ID
  3. --------------------- ------------------------------ ----------
  4. COMMODITY COT_IND01_P100 4004126
  5. COMMODITY COT_IND01_P37 4004063

可以看到,分割槽37和100都被鎖定了。鎖定37分割槽是意料中的事,因為要從該錶轉移資料。那為什麼要鎖定第100個分割槽,也就是最後一個分割槽呢?

我的理解是:新增加分割槽的位置101是由原分割槽表的分割槽數100確定的,如果在增加分割槽的過程中允許對原表最後一個分割槽100作DDL操作,如coalesce操作,則新加的101分割槽就不一定是從原來的分割槽37分配資料了,101分割槽本身應該是新的第100分割槽,這樣就引起混亂了。到這裡,你可能會說,按這理解,是不是其他的分割槽也應該鎖定呢?其實不用,因為hash分割槽表是不支援drop partition操作的,而只支援coalesce操作來實現類似的操作,但coalesce只能從最後一個分割槽開始收縮。

瞭解了增加hash表分割槽過程中鎖資訊的實際指導意義是什麼呢?

繼續上例中的討論,由於分割槽37和最後一個分割槽100會被排他鎖定,因此在新增分割槽過程中這兩個分割槽是不能作DML操作的,因為DML操作需要在分割槽上申請共享鎖(mode3)。也就是操作這兩個分割槽的應用會受到影響。

Hash表增加分割槽不會像其他型別分割槽表,如range分割槽那樣能夠迅速完成,因為這裡新增分割槽的過程中是要有IO操作的,要轉移資料到新的分割槽。其實這還不是最主要的,由於Hash表是根據分割槽鍵Hash函式值來決定分割槽的,新增分割槽的主要時間其實是花在了計算hash值上。在上面的測試中,新增新分割槽操作的消耗時間是6分58秒,從下面的10046統計資訊可以看到,其中6分鐘都是花在了CPU操作上,相信主要是Hash運算引起的。

  1. OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS
  2. call     count       cpu    elapsed       disk      query    current        rows
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    Parse      328      0.17       0.27          0          0        148           0
    Execute   1520    360.14     396.30     456820   11416202      26357    11565252
    Fetch     1767      5.42      21.18      21421      26540          0        2862
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    total     3615    365.73     417.76     478241   11442742      26505    11568114

該測試案例中分割槽COT_IND01_P37中共有接近1千萬條資料,耗時接近7分鐘,假設分割槽資料達到了1億條,則耗時應該在1個小時以上。如果我們的Hash分割槽數按Oracle的建議為2的整數冪,則我們在增加分割槽時是要增加原有分割槽一倍的新分割槽,比如原分割槽為128個,擴充套件的時候需要增加128個分割槽,乘以每次新增分割槽需要的時間,則為Hash表增加分割槽將是一個很恐怖的操作。

總之,Hash分割槽有其優勢,但也有嚴重的缺陷,比如這裡描述的分割槽擴充套件問題。因此在專案設計之初,我們就需要慎重選擇分割槽數。但是隨著資料量的增加,我們又很難避免為分割槽表增加分割槽的操作,這種操作是很耗資源的操作,操作過程中由於鎖的問題會影響對原有某些分割槽的操作。但如果我們因為畏懼前面存在的問題拖著不作分割槽擴充套件,則越是往後,隨著資料量的增加,這種增加分割槽的操作越難以實施。

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

相關文章