最近一段時間在看一些數據庫內部實現的相關知識,一直想找個時間總結下,感受學習這些知識會以爲挺有意義的,做爲一個開發人員你可能不會本身被要求去實現一個數據庫,不過你應該要去理解數據庫的實現原理,這樣你才能根據你的場景更好的優化你的應用,而後也會在針對你的應用在進行數據庫選型時提供幫助。mysql
目前的話,市面上有各類各樣的數據庫,有sql,nosql,newsql具體能夠看我前段時間總結的一篇文章形形色色的數據庫,那麼他們的實現究竟是怎麼的?或則都是用的什麼存儲引擎?再而後存儲引擎各自的優點,應用場景是什麼,我看了一些資料,這裏簡單總結一下,文末提供了相關資料。git
最簡單的數據庫github
用vim 定義db shell文件,輸入下變的內容,就實現一個最簡單的數據庫,只有set, get方法redis
#/bin/bash
db_set() {
echo "$1, $2" >> databases
}
db_get() {
grep "^$1," databases | sed -e "s/^$1,//" | tail -n 1
}
[[ -z "${1-}" ]]
case $1 in
db_set|db_get) "$1" "${@:2}" ;;
*);;
esac
複製代碼
set數據算法
$ ./db db_set test_key test_value
$ cat databases
test_key, test_value
複製代碼
get數據sql
$./db db_get test_key
test_value
複製代碼
簡要分析shell
簡要的分析一下,上邊的數據庫經過set方法會一直append文件到databases文件中,只要數據存儲充足,對於數據庫來講是順序寫入,因此性能是很高的,這塊至關因而log操做,只有append。而後是get方法,每次都須要grep整個數據庫文件,至關因而全表掃描,從算法的角度來分析複雜度會是O(N),隨着數據文件的增長,性能會愈來愈低,速度會很慢。數據庫
因此爲了優化存儲get方法結構,咱們須要額外引入一個結構,索引結構(這個先不考慮併發控制,事務,範圍查詢等等常見的功能),索引的詳情能夠看連接,簡單來講它的目的是保存key和value的對應關係,就是位置對應關係,因此能夠幫助咱們提高查詢性能,同時也會帶來一些弊端,這就要求在數據插入前同時更新索引數據,保持索引數據和內容數據的一致性,會影響寫入性能。正式這樣的緣由,數據庫默認是不會開啓索引的,索引建立會是很影響數據庫的性能的一個指標,須要根據不一樣情形建立合適的索引,索引太多,會影響寫入性能,索引太少,影響查詢性能,因此找到一個合適的平衡點,是很是重要的,大多數狀況下,索引都是須要dba和開發一塊兒評估。vim
HASH索引數組
先用最簡單索引結構來分析,hash索引,hash是一個很是基本的算法,具體能夠看連接。這裏列舉一個主要利用hash存儲引擎的數據庫Bitcask,官方文檔詳細的介紹了其實現原理,簡單來講就是這個該數據庫在內存中是一種hash表結構,hash結構維護key到文件存儲的position,數據僅支持追加操做,全部的操做都是append增長而不更改老數據,數據文件分爲新數據文件和老數據文件,老的數據文件只讀不寫,只有一個數據更新,及活躍數據文件。
A Log-Structured Hash Table for Fast Key/Value Data
數據結構以下:
Bitcask在內存中保存了key,value的位置關係,索引關係,key對應數據key, value保存不是實際的數據值,而是保存對應文件的相關信息,從圖中能夠看到是 file_id, value_sz, value_ops, tstamp,因爲內存實際不保存真實數據,全部存儲容量就會大大提高。
file_id 表明保存的具體文件id
value_sz 表明value大小
value_ops 表明在文件中具體位置
tstamp 保存時間
而後還有一個點是,該數據實現的高性能是依賴於文件追加的方式,至關於LSM的數據追加,數據增長,刪除,更新都是追加操做,造成新的數據版本,老的數據仍是會在磁盤上,因此操做對應的io都是順序io,不會隨機訪問磁盤,因此寫入性能獲得了保證,而後對於過時的數據,髒數據,bitcask會按期在後臺進行一個merge操做,將inactive的數據清除。對於讀操做就不須要過多介紹了,經過key能夠直接讀取到values_文件位置,性能也能獲得保證。
因爲全部的數據都是保存在內存,全部服務或則主機掛掉,內存中的映射關係就會丟失,因此須要將內存中的hash結構數據數據持久化到磁盤,當數據服務從新啓動,會從磁盤恢復對應關係。
下圖是數據文件中保存的內容
實際文件中具體記錄的數據以下,包含crc校驗值,key, value等。
試想一下,若是上邊shell數據庫,精簡優化,利用hash索引保存key,value映射關係,那麼就能提升數據的訪問速度,避免全表掃描這種操做,這也是hash索引能帶來的最大優點,可是試想一下,你的數據須要保證有序性,key有必定的順序,那麼hash索引就不能解決這類問題。
LSM
引入LSM算法能夠保證寫入性能的狀況下支持還範圍排序的。
首先說一下什麼是LSM算法(Log Structured-Merge Tree), 最初LSM來自於Google分佈式表格系統BigTable的論文,它是bigtables的文件組織方式。目前LSM已經應用到了多種數據庫領域,最簡單的是LevelDB,而後是facebook針對其更改的Roskdb,後來又產生了帶上Hbase, cassandra數據庫,在mongdb Wired Tiger也能看到,最近也出如今了MySQL領域,已經被普遍驗證的存儲引擎方式。
這裏我簡單介紹一下該類數據庫的流程(這裏以leveldb結構介紹)
LSM會維護一個內存有序的Memtable,在內存中維護這樣一個有序的數據結構會很容易,像紅黑樹,平衡樹,當有數據插入是先寫入到Memtable保持平衡,有序,當Memtable到達必定大小會觸發Merge操做,首先將源Memtable指針賦值給一個不可變的MemTable, 這裏叫Immutalbe Memtable,而後會從新生成一個Memtable,後續的操做會新的Memtable中操做,產生的Immutalbe Memtable會持久化爲.sst文件,因爲數據已是有序的了,因此持久化也會很容易。
而後sst文件會是一種層級文件,sstable中的文件也是根據主鍵順序排序的,每一個文件都會記錄其中記錄中的最小值和最大值(查詢速度會有所保證)。leveldb中manifest文件保存了每一個sstable中的範圍信息,和層級信息,leveldb在運行中 sstable也會進行合併,老的數據也會被清除,manifest保存了相關過程,低level的文件會合併爲高level的文件。
這裏想談一下log文件,這裏的log會記錄寫操做,就是因此寫操做前必須將數據有限順序寫入到log中,而後再寫入到內存中,這裏你會想起數據庫的redo log實現相似的事情,這裏很簡單,和其餘標準數據庫同樣,爲了解決內存數持久化到磁盤中可能出現的故障,如機器異常,內存故障等問題,優先順序寫入磁盤,因爲這裏是順序的磁盤io,全部性能也不會有很大的順勢,能夠這麼說,大多數數據庫爲了保證數據的可靠性都確定會有預先寫log操做,固然該log也不會無限增大,大多數數據庫會採用checkpoint保存必定時間數據變化。
LSM的寫操做和Bitcask同樣,是經過append log的方式,只是會附加一點須要保持數據在內存中有序,不過插入,更新,刪除都仍是順序io,性能會有必定保證。而後後臺會按期合併各個sstable,清除沒必要要數據。這裏可能引入的一個性能瓶頸是讀取性能,若是是查詢一個不存在的key,會先查找Memtable,而後會查詢最新的sstable文件,這裏會引入一個布隆過濾器來解決該問題,簡單來講他能很快的斷定某數據集中是否存在某個key。
最後簡單來講,在保證必定寫入和讀取性能的條件下,比hash index的優點在於,提供了有序的數據集,支持了範圍查詢。
BTREE
上邊介紹的hash和lsm都不是最應用普遍的索引,btree纔是應用最普遍的數據結構。
B+索引是數據庫中最多見,也是使用最頻繁的索引結構,下面這裏能夠經過一些基礎數據結構來解釋會好一點。
相信你們都瞭解過一種基礎的算法,二分查找法,以有序中點爲間隔,使用跳躍式的方式,能快速將一組有序的數據結構中找到須要查找的值。如在下列數組中查找到48,使用3次就能夠了。
相對應的樹數據結構是二叉查找樹,二叉查找樹定義是,二叉查找樹左邊的葉子節點老是小於父節點的節點的值,右節點老是大於父節點的值。該樹能夠經過前序,或則中序遍歷獲取到有序數組數據,而後經過二叉查找獲取到數據。
有序性能提升查詢效率,有序性也能給數據庫帶來支持像範圍查詢提供條件。
其次爲了更好的提高查詢性能,咱們應該減小樹的層級結構,如平衡二叉樹(符合二叉樹的定義,增長條件: 任何兩個節點的高度差不得超過1),咱們須要儘量保證一棵樹的平衡,不過保證一棵樹的平衡須要帶來必定的開銷(經過旋轉維護平衡),因此設計數據結構須要考慮查詢效率,也須要考慮維護一棵樹的成本。
btree,二叉樹,平衡二叉樹同樣都是經典的數據結構。是由B tree演進而來,關於B+ tree,能夠看鏈接詳情。簡單來講就是 b+樹的全部節點都是按照大小順序的保存在同一層的葉子節點上,各個葉子節點經過子節點的指針進行鏈接,以下圖。
這裏簡單說一種最經常使用的數據庫Mysql Innodb,Innodb由btree實現索引,和hash索引相比,因爲有序性,因此Innodb支持範圍查詢,在Innodb中,有一種叫彙集索引的特殊索引,他會保存將具體的數據行內容。
Innodb是按照page的方式來管理數據,每一個page對應一個節點,葉子節點保存了具體的數據,非葉子節點保存了索引信息,數據節點在b tree+ 有序,查詢來說是從根節點二分查找,查找對應的數據,若是數據不存在,就從磁盤中讀取,而後更新內存數據,修改數據和前邊內容同樣,都會先寫入日誌(防止異常狀況數據丟失),這裏對應redo log。對應插入操做會比較複雜,這裏就不分析了,簡單來講就是爲了保持b tree+樹的平衡,會帶來樹的旋轉和分裂,分離過程都是內存操做是很是危險的,因此也會進行也須要先寫入redo log。
從這裏看出,爲了保持內存中樹的平衡,維護btree+這樣一個數據結構,須要作不少工做,性能會有必定下降,主要是插入,更新,刪除都不是上邊兩種方式,經過append 文件,這裏的插入,更新,刪除,會查找到具體的內存位置,而後是磁盤位置,進行磁盤數據的更新,這回產生大量的隨機IO, 寫入,更新,刪除,這類型的隨機IO會更多。
LSM和Btree因爲實現原理的不一樣,因此不過也都有各有優缺點,LSM因爲順序寫,提供更快的寫入性能,Btree因爲更加平衡,讀取性能會更強。
總結
雖然這些年層出不窮的出現了各類數據庫,可是其實不少數據庫底層的存儲引擎仍是有不少類似性,能夠看到各種存儲引擎實現方式應用的場景都有必定的不一樣,好比hash索引更適合簡單的kv存儲,適合作緩存,lsm適合高併發的數據寫入,btree更適合大量查詢的應用場景,因此在實際應用的時候,咱們須要考慮咱們的應用場景,若有大量查詢的場景(OLTP),像電商,就更好使用btree引擎的數據庫,如mysql, oracle。須要高併發寫入的場景,如IOT設備寫入,用戶訪問日誌記錄等,可使用LSM爲基礎實現的分佈式nosql hbase,cassandra等。再而後對於簡單數據操做,頁面緩存,熱點數據緩存,可使用hash結構的memcache,redis等。
最後,推薦看一下leveldb和boltdb的源碼,代碼比較少,更容易數據庫實現原理。
參考文獻
designing data-intensive applications
https://github.com/google/leveldb