自己動手寫數據庫(三) 持久化方案與索引樹

自己動手寫數據庫(三) 持久化方案與索引樹

推薦一點資源

之前說我所有實現都是Go寫的,在MonkeyDB2@Github,但是有些同學可能不喜歡Go的風格或者設計思想,
在此推薦大一學弟的項目,Java寫的:CauchyDB

內存分塊管理

既然要實現一個數據庫,我們之前所有的東西都是在內存裏面的,這如果服務器關機,那內存裏面的數據還不得全部GG啊~所以我們需要對內存進行管理,
將所有的數據操作在我們所管理的內存中進行,無論是索引頁還是數據頁。但是這樣還有一個問題,指針所指的地址,在重新啓動後就不見了,這該怎麼辦呢?
我們可以採用兩種方法:1.對於索引樹裏的孩子指針等指向本頁的指針,使用相對於本頁的偏移值存儲指針而不是存儲實際地址,這樣在恢復之後,不需要改動
即可正常使用;2.對於指向本頁以外的指針(例如索引頁指向數據頁的指針),使用頁首地址+偏移值的方式儲存(在MonkeyDB中,我們認爲現在的內存大多是
小於1TB的,而我們設置的內存塊大小是16M,這樣1TB*16M=64位尋址空間,使用一個8Byte的字段即可記錄頁首地址和偏移),這裏我們爲什麼不直接記錄指針
地址呢,因爲我們要恢復的時候是以內存塊爲單位恢復,我們會用一個表來記錄恢復前後的地址,因爲記錄的是塊首地址,所以記錄時也是用首地址+偏移。

併發控制

另外,數據庫不得不考慮的一個問題就是併發控制,併發控制一般採用讀寫鎖的形式,關於讀寫鎖,可以參見博客:平凡的程序員
我們自己分塊管理了內存之後,併發的問題就不難解決了,不論是索引頁,還是數據頁,我們都繼承自內存塊,然後對內存塊進行併發控制即可解決。這裏具體
做法,就是對內存塊進行讀寫IO操作的封裝,在封裝的函數中加鎖,類似下面這樣(僞代碼):

DataBlock {
    void*       rawPtr
    unsigned    size
    Mutex       mutex
}

DataBlock.read(unsigned offset, size) (char[], error) {
    this.mutex.RLock()
    if offset + size > this.size {
        this.mutex.RUnlock()
        return NULL, OUT_OF_SIZE
    }
    this.mutex.RUnlock()
    return this.rawPtr[offset:offset+size], NULL
}

//write like read

索引樹

數據庫常用的索引樹是B+Tree,關於B+Tree索引的詳細內容,可以參見:傳送門,關於
爲什麼使用索引樹,這裏就要引入時間複雜度的概念:時間複雜度和空間複雜度詳解。瞭解
了時間複雜度,那麼對於數據庫這種動不動就幾百萬幾千萬的數據量,索引樹的log(n)和線性搜索n的差距,可想而知(基本就是幾萬倍以上差距)。而B+Tree
相對於二叉搜索樹,紅黑樹等搜索樹的優勢在哪裏呢,這裏又有一個CPU緩存和磁盤IO原理的問題,這裏需要注意的是,索引樹基於我們封裝的內存塊,所有IO操作也
繼承自我們的內存塊,這樣就可以保證索引樹的併發控制和持久化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章