關系型數據庫中的壓縮技術
計算機存儲的容量限制仍然日益成為IT系統的瓶頸。其主要原因有兩個:第一,信息革命導致人們產生了比過去多得多的數據。巨大的數據庫系統每時每刻都在產生海量的新數據。第二,隨著計算機存儲能力的增長,人們傾向于永久性保存所有的數據。例如,在信息革命早期,證券交易系統往往只保存近一段時間的交易細節數據。如今,人們傾向于保存所有能夠被保存的數據:每一次交易,每一通電話,網站的每一次點擊,交換機中的每一回通信等。
在這種趨勢下,計算機存儲承擔著越來越沉重的壓力。尤其是在企業級應用中,為了保存海量數據而在存儲上投入的成本,往往已經到了令人吃驚的地步。
在數據庫中使用壓縮技術,是為了解決(或者至少緩解)這種壓力所做出的努力之一。這種技術的定義十分簡單:對存儲在數據庫中的數據進行壓縮,從而減少占用的磁盤空間,同時又盡量不影響數據庫的其他操作。
很容易想象這一技術產生的后果。被壓縮后的數據能夠顯著地減少占用的磁盤空間,從而降低整個系統的存儲成本。然而對數據進行壓縮和解壓縮,需要更多的CPU時間。在對速度要求十分苛刻的數據庫系統中,這種CPU時間的額外支出,是否會導致效率的嚴重降低呢?
讓我們全面地審視壓縮技術引起的得失。在CPU時間上,會有額外的支出。但是,由于壓縮后的數據占用的磁盤空間減少了,這意味著系統用于I/O的時間也會相應的減少。眾所周知,數據庫系統最大的瓶頸在于I/O:I/O速度的增長卻遠遠跟不上CPU按照摩爾定律的增長。因此,從CPU時間上支出的成本,可以在I/O速度的提高上補償回來,而且還可能有盈余。壓縮后的數據庫,不但會占用更少的磁盤空間,甚至還可能有更快的速度。
然而在實際項目中,還要考慮到多種因素的權衡,情況可能會非常復雜。幸運的是,主流的幾種關系數據庫在實踐中都已經正式支持壓縮技術。目前,數據庫壓縮技術主要仍然被應用于數據密集型的OLAP,而不是運算密集型的OLTP,但這并不意味著它不能被應用于OLTP。
壓縮方式
目前,幾乎所有的關系型數據庫中應用的壓縮方式,都是基于字典的壓縮方式。基本原理是,將數據中重復出現的信息抽取出來,并用比較簡短的符號予以代替,從而達到壓縮的效果。舉例來說,如果數據中重復出現了“PersonalComputer”這個字符串,那么它就會被識別為一個模式(Pattern),然后所有這個字符串出現的地方都會被一個對應的符號(Symbol)代替,比如數字1。所有的模式和對應的符號都會被存儲在字典里面(Dictionary),字典被用于壓縮和解壓縮(也就是對Pattern和Symbol進行相互替換)。當然,真實的應用比這要復雜得多。但是,理解了字典壓縮的原理以后,我們已經可以從不同的角度對不同的壓縮技術進行區分。
按建立字典的方式區分:手工建立字典和自動建立字典。手工建立字典,意味著數據庫不能自動搜索數據中的重復數據,必須人工輸入所有的模式才能建立字典。這種方式出現在數據庫壓縮技術的早期,目前已經基本被淘汰。自動建立字典則意味著數據庫會自動搜索模式而無需人工干預。
按字典應用的范圍區分:表級別的字典和塊級別的字典。表級別的字典意味著在整個表的范圍內搜索模式并建立一個唯一的字典,而塊級別的字典則在每一個塊上建立單獨的字典。其中,塊是關系型數據庫中的一個術語,是存儲的最小單位。
按存儲的方式區分:列壓縮和行壓縮。這涉及列存儲和行存儲的概念。行存儲表示數據庫中包含不同字段的同一行被連續存放。列存儲則表示包含不同行的同一字段數據被連續存放。同一字段的數據出現重復的可能性較大,這意味著基于列的壓縮可能有更高的效率,但這和傳統關系型數據庫的存儲方式相悖。由于二者互有利弊,數據庫廠商往往通過一些技巧來避免其缺陷,使之適應實際使用,甚至混合使用這兩種壓縮方式。
壓縮相關的操作
雖然關系型數據庫使用的壓縮算法本身不太復雜,但是由于壓縮技術改變了數據存儲的底層結構,因此涉及數據庫操作的方方面面。下面是一些主要的相關操作:
數據查詢。當接收到查詢請求時,數據庫系統從磁盤中讀取已被壓縮的數據,必須先經過一個解壓的過程,將數據還原為未壓縮的形式,再返回給查詢請求。
數據更新。當進行Insert和Update操作時,數據需要經過壓縮之后才被存儲。理論上來說,Delete操作只需要簡單地刪除數據,而無需進行壓縮或解壓縮。但是事實上,在某些自適應的壓縮技術中,對已有數據的更新到達某一閾值時,會導致字典的自動更新(因為字典已經不能再適應當前的數據)。這意味著,IUD操作都有可能導致字典的重新創建(或刪除)。
數據裝載。這和插入數據的過程類似,數據將會先被壓縮然后被存儲。在某些情況下(例如,當DB2的AutomaticDictionaryCreation技術被啟用時),裝載數據時還可能同時創建字典。
表整理。在整理表時,根據當前表被標識為壓縮或未壓縮,將會對數據進行相應的壓縮或者解壓縮處理。表整理是對整個表進行充分壓縮的有效手段。
壓縮率評估。數據庫一般會提供一個操作,在未被壓縮(或未被完全壓縮)的數據表上進行評估,預測能達到多高的壓縮率。
索引(Index)壓縮。索引壓縮的算法與關系型數據壓縮不太一樣,本文不進行深入討論。
大對象(LOB)壓縮。大對象不使用關系型數據的行存儲或列存儲方式,因此也不適用上述的算法。
日志(Log)。日志中需要保存和壓縮操作相關的信息,以保證數據的一致性。
備份與恢復。在備份與恢復操作時,需要進行相應的數據壓縮和解壓縮處理。
壓縮相關的命令
雖然壓縮涉及非常復雜的數據庫內部機制,但理論上來說,壓縮后的數據庫對于使用者是透明的,所有的壓縮和解壓縮過程都隱藏在數據庫內部。因此,在絕大部分情況下,使用者不需要進行額外的操作,甚至不需要知道數據庫是否已經被壓縮過。
當然,仍然有一些與特定的壓縮相關的數據庫命令。下面以DB2V9.7為例,作一簡單討論。
當創建一個表的時候,可指定該表使用壓縮。語法如下:
view sourceprint?1 CREATETABLECUSTOMER( … )COMPRESSYES;
這條命令創建了一個名為CUSTOMER的表,并且在此表上使用壓縮功能。以后在此表上進行的查詢、插入、更新等操作,都會自動在后臺執行相應的壓縮和解壓縮操作,而不需要使用特殊的語法。
對于一個已經存在的表,可以用ALTERTABLE命令來啟用或停止其壓縮功能:
view sourceprint?1 ALTERTABLECUSTOMERCOMPRESSYES;
這條命令會在已存在的表CUSTOMER上啟用壓縮功能。
view sourceprint?1 ALTERTABLECUSTOMERCOMPRESSON;
則會關閉壓縮功能。
需要注意的是,使用ALTERTABLE命令啟用或停止其壓縮功能后,并不會對已經存在的記錄生效。舉例來說,在對某個表啟動壓縮后,新插入的(包括被批量load的)記錄以及被更新的記錄都會以被壓縮的格式保存,但是,對于那些已經存在、并且沒有在壓縮功能啟用后被更新過的記錄,它們仍然以原始的格式保存。這說明了為什么在啟用壓縮以后,數據庫占有的磁盤空間并沒有立即減少。
那么,怎樣使壓縮功能對于已經存在的記錄也生效呢?答案是使用REORG整理整個表。如上例,在執行完ALTERTABLE命令以后,立即執行以下命令:
REORGTABLECUSTOMER;
這條命令將掃描整個CUSTOMER表,并對所有的記錄都進行壓縮或解壓縮。根據表的大小,可能會執行相當長的時間。
壓縮率
即使在相同的壓縮算法下,表的壓縮率也是不固定的。與常規的文件壓縮相比,表的壓縮率取決于更多的因素。
數據冗余度。數據庫壓縮的前提是被壓縮的數據是冗余的。對于冗余度大的數據,往往能得到很好的壓縮率。而對于冗余度很小甚至趨近于0的數據,再好的壓縮算法也無濟于事。不同數據庫的冗余度差別是非常大的。
數據分布。某些類型的壓縮算法會在一個小范圍內構建壓縮字典。這意味在某個較小的范圍內,如果可以找到較多的Pattern,就可以獲得較高的壓縮率。如果Pattern是均勻分布在整個表中的,那么小范圍內的字典將效率很低。在表的某些列上建立聚集索引,強制使內容相似的行處于相鄰的位置,往往是提高壓縮率的一種有效方式。
數據庫群集。在群集的情況下,由于字典無法跨越群集,因此必須盡量使單一節點上的數據冗余度盡可能地高。對于DB2來說,精心設計DPF中的HashColumns,以及配合使用聚集索引,能夠有效地影響到壓縮率。
某些基于真實應用的數據庫的測試表明,壓縮技術往往可以將數據庫的大小減少70%甚至更多,同時對系統的整體性能沒有明顯的影響(在I/O密集的系統中,系統性能甚至可能提高)。當然,這個測試結果僅供參考,因為實際的表現依賴于數據庫的具體情況。
結論
前面探討了關于數據庫可用性和擴展性方面的問題,我們看到每種產品和架構都是有缺陷的,其實架構就是有所取舍的過程,目標是用最小的代價去解決問題。所以找到適合自己的產品和架構,這才是最重要的。