1. 程式人生 > >資料庫查詢的實現:B樹與磁碟I/O演算法設計

資料庫查詢的實現:B樹與磁碟I/O演算法設計

資料庫查詢的實現

  對記憶體中的元素進行查詢(query)十分快速,因為記憶體的速度十分快,所以可以隨意I/O記憶體,當然這裡的”上家”指的是CPU的快取。至於快取(Cache),那速度就更快了。但是網際網路的發展,已經不僅僅需要記憶體查詢,而是需要大型資料庫查詢。要把一個數據庫放在記憶體裡執行代價很昂貴,雖然有人那麼做,但畢竟不是主流。很多巨型庫的資料量非常大。

1. 檔案儲存在低速硬碟上

  所以首先是結合一下實際情況:檔案是儲存在硬碟上面的。當下硬碟的讀取速度十分有限,與RAM相比差了好多個數量級,當下最快的SSD可能是intel 900P ,這個是基於新技術3D Xpoint的壽命超長、速度超快的SSD。儘管它的I/O都是2GBps-3GBps,但是這個速度與RAM比起來還是顯得可憐。

  所以在進行查詢定位某個資料的時候,應該儘可能地減少磁碟I/O次數。這裡必須要明確一點:一個檔案裡面寫有相當多的資料,而我們要定位的資料就在一個或者多個檔案裡。當我們要定位檔案裡的資料時,首先要進行讀檔案(read file),但是一個檔案可能相當大,大檔案很難進行一次性記憶體讀入,甚至在很多情況下不能把一個檔案讀入記憶體!如果有Linux運維經驗的同學可能知道,檢視一個.log日誌可能無法完成,log日誌可以無限制擴大,從而產生一個龐大無比的純文字檔案。網路web日誌更是如此,在資料量級上,web伺服器日誌所處位置很高。能開啟好多個GB大小的文字已經成為了此一種技術優勢,常見的Notepad++做不到,EmEditor卻可以。

2. 磁碟的地址劃分

  磁碟與記憶體一樣,都是有地址的,至於為什麼會有地址,那是因為組織檔案的需要。
  
  不用特別瞭解組織檔案的具體形式,只需要記得,磁碟是有儲存結構的,每一個區塊,都有自己的編號。具體說來,就是所在盤號(這可不是C盤D盤之類的號,而是一塊物理硬碟上,具體的第幾塊碟片)、上下面、哪個單元,這一系列資料通過一定格式,就被定義為了磁碟地址,這個地址是真正的實體地址,而不是邏輯地址。

  磁碟可以被劃分為不同大小的簇,一個簇可以存放幾十KB到幾MB不等大小的資料。而與之相比,磁碟簇的編碼所用字元實在是微不足道。
  
  磁碟的內部結構

  上圖是磁碟結構。看完這個結構圖,就可以明白希捷(Seagate)的單碟1TB技術是什麼意思了。搞軟體、搞演算法必然要懂點硬體。這樣才能知道很多東西是為什麼要被製造出來的!

話不多說,明白了磁碟的地址標定,就可以進行演算法設計了。

3. 查詢函式的設計

  要問演算法與資料結構誰重要,我認為是資料結構。因為結構設計儲存結構,所以與硬體水平相關;同時演算法是依賴於資料結構的,基本上資料結構定下來了,演算法也就確定了。所以資料結構應該更重要。

  弄明白了磁碟儲存的形式,就可以有針對性地進行查詢函式的設計了。比如,我們建立了一個儲存體積相當大的學生名單,學生名單表的主鍵之類的資料都是完善的(這裡有必要提及一下關係型資料的基本)。我們要在這個表中查詢一個叫吉米的學生的相關資訊。這時候如果按照一個一個地比姓名的方法,就非常低效。如果有N個數據元組,CPU一次讀取可以讀k個元組,那麼我們要進行Nk次硬碟讀取,k一般是有限的,N卻有可能非常非常大,這樣一來這個I/O次數有可能就非常恐怖,導致查詢一個數據異常緩慢!

  一次比一個效率不高,那麼能否一次比一批資料呢?這個時候我們可以參考一下二叉搜尋樹的設計。二叉搜尋樹十分簡單,就是針對一個有序的樹進行的查詢,對於一個非子節點的節點,其左子樹的所有資料都小於該子節點的數,右子樹中的所有資料都大於該子節點的數值。

  如果用一棵二叉搜尋樹儲存這個檔案,那麼查詢起來就非常簡單了!因為在每一層進行一次比較就可以拋棄一棵子樹的資料,所以I/O複雜度為O(log(Height)),比較簡單吧。

  接下來的第一個問題是二叉搜尋樹好不好?很顯然它還不夠好,因為二叉搜尋樹有可能很高,這樣進行查詢的複雜度還是有點大。這裡要特別注意I/O複雜度這個概念,這個概念不是CPU複雜度,而是說,目前制約速度的主要因素是I/O而不是其他的。二叉樹要是很高,定位一個數據就可能要一層一層地走下去,走一層就進行一次I/O,至於為什麼這麼說,我們要結合演算法進行說明。

  二叉搜尋樹可能很不平衡,所謂的平衡是左右大小一致。如果root的左子樹很茂盛,而右子樹基本沒有紙業,那這就不是平衡樹。考慮不平衡的一個極端情況:任意一個非葉節點,僅有左子樹(右子樹也一樣)。這種極端情況,與那種線性表無區別。而且它的高度還是N ,基本沒什麼用。

  知道不好,那麼能否把這棵樹做平衡了呢?當然可以,親愛的資料結構與演算法課早就給出了平衡演算法。但是平衡演算法也不好,大醫治未病,出了問題再找醫生不好。有一種樹叫做平衡樹,如AVL樹、(2, 4)樹,紅黑樹。這樣的樹可以保證在建樹過程中,一直是平衡的。這可以很好地避免一些問題。

  樹做平了,是不是就夠了?當然沒有。如果能讓樹變矮一點就更好了。B樹橫空出世。  

  把一棵樹存在硬碟上,通過鏈式結構的形式進行存放。

  • 根節點(root)
  • 子節點(offspring)

  節點上面存放一些結構化的線性資料。

  節點單純存放資料不行,為了方便查詢還需要有一定的冗餘儲存。這一部分冗餘儲存就是磁碟簇的地址。在初始化這棵樹的時候,這棵樹必然是存放在記憶體中,這樣我們就可以按照鏈式結構進行存放。
  
  當記憶體中的初始化工作已經結束,可以先申請幾塊磁碟簇(這裡的數量是有嚴格規定的,具體與磁碟簇的大小有關),第一個磁碟簇,存放這棵樹的根節點的數值,同時存放下面幾個節點的磁碟地址。同理,存放其他的元素亦如是,直到存放完所有的樹。
  
  這時候,先不論如何更新這棵樹,先看如何查詢:2中很明顯地體現了這個過程。每一個節點中的紅色,是元素,黃色是地址。紅色的數目不超過2,說明一個磁碟簇不能存放超過兩個元素。當然實際過程中遠比2要大。有了這棵樹,就可以對檔案進行檢索。所以從中可以看到,巨型文字檔案被分成了好多個簇儲存在了硬碟上。而這也正是磁碟儲存的實質,千萬不要以為檔案在光滑的碟片上是連續的一整個區段!
  
  鏈式儲存帶來的好處,顯而易見,那就是Insert的時候,不需要搬移很多資料,直接申請地址就可以了。就像C中的malloc函式。
  
  B-Tree

  關於B樹,這裡就不詳細介紹了,他與紅黑樹、2-4樹類似,也是一種平衡樹,而且隨著每個“簇”中檔案的變多,一層之下可以存放相當可觀的資料,從而遍歷極少數的資料,就可以定位。當然,這樣造成的I/O次數也比較少。更適合檔案系統的B+樹,下次介紹。

[1]: http://www.elimu.net/Secondary/Kenya/KCSE_Past_Papers/KCSE%202010/Computer%20Studies/KCSE_2010_COMP_P1/Images/Hard_Disk.png

[2]: http://hi.csdn.net/attachment/201106/7/8394323_13074405906V6Q.jpg