數據庫最基本兩個功能:數據的存儲和數據的查詢。 當咱們寫入數據時,數據庫能夠存儲數據;當咱們須要訪問數據時,數據庫能夠給咱們想要的數據。 數據庫會經過特定的數據模型和數據結構存儲數據,並支持經過特定的查詢語言訪問數據。本文將從最簡單的數據庫開始,討論數據庫如何存儲數據,如何查詢數據。本文將討論兩種存儲引擎:log-structured 存儲引擎和以B+樹爲表明的page-oriented存儲引擎。html
#!/bin/bash #key,value對追加寫入文件的最後一行 db_set () { echo "$1,$2" >> database } #查找指定key的最後一行的最新的value db_get () { grep "^$1," database | sed -e "s/^$1,//" | tail -n 1 }
上面兩個Shell函數實現了最簡單的Key-Value數據庫。 調用db_set能夠寫入數據,調用db_get能夠查詢數據,數據的物理存儲格式是逗號分隔的普通文本文件。mysql
bash-3.2$ db_set 1 kks bash-3.2$ db_get 1 kks bash-3.2$ db_set 2 kangkaisen bash-3.2$ db_get 2 kangkaisen bash-3.2$ db_set 1 KKS bash-3.2$ db_get 1 KKS bash-3.2$ cat database 1,kks 2,kangkaisen 1,KKS
其中db_set函數擁有很好的寫入性能,由於是追加寫;可是db_get函數的性能十分糟糕,其時間複雜度是O(n),咱們每次必須全表Scan。git
爲了可以快速找到特定Key對應的Value, 咱們須要引入一個數據結構:Index。 所謂Index,就是咱們在數據庫中增長額外的元數據,而後Index像路標同樣能夠快速知道咱們須要訪問數據的位置和偏移量。 Index相似漢語字典中的索引和通常書籍中的目錄。若是咱們須要按照不一樣的方式訪問相同的數據,咱們有可能須要多種不一樣的索引,好比按照Key查詢和按照Value查詢,咱們會分別須要針對Key的索引和針對Value的索引。github
Index是基於原始數據衍生的附加的數據結構,增長索引必然意味着下降數據寫入速度,增大存儲空間,因此Index是以數據寫入時的處理成本和存儲的空間成原本換取查詢的加速。這也是數據庫設計的一個trade-off,不一樣索引的查詢加速比,寫入時的處理成本,存儲的空間成本每每是不一樣的,因此在設計數據庫時選擇何種索引是一個很重要的點。算法
下面就讓咱們用Index加速以前最簡單的Key-Value DB。以前咱們db_get方法查詢特定Key必須全表Scan的緣由,是由於咱們不知道特定Key在文件中的Offest,假如咱們知道了每一個Key的Offest,咱們就能夠直接Seek到Key對應的Offest,直接讀取Key對應的Value。而Key到Offest的映射咱們天然會想起到咱們熟悉的數據結構HashMap,咱們能夠在內存中維護一個HashMap,HashMap的Key就是Key-Value DB的每一條記錄的Key,HashMap的Value就是每一條記錄在文件中的Offest。sql
有了HashMap後,咱們每次寫數據後就必需要更新HashMap,查詢數據時先從HashMap獲取特定Key的Offest,再直接Seek到文件對應Offest的位置,讀取數據。 事實上Bitcask(Riak的默認存儲引擎)就是這樣作的。數據庫
不過顯然Hash Index有兩個缺陷:bash
目前爲止,咱們都是把數據寫到一個文件中,這顯然是不合理的。 一個常見的作法就是將文件按照大小拆爲爲Segment,每一個Segment是不可變的。 Segment的概念很常見,好比Kylin和Druid中都有Segment的概念,指必定大小或者必定時間內不可變的文件。數據結構
第1部分咱們知道,咱們同一個Key的Value的更新只是追加寫入,並無刪除舊的Value。 當咱們有了多個Segment後,咱們天然就能夠按期在後臺執行Compaction操做,將同一個Key的舊Value刪除,更進一步,若是咱們數據庫支持delete的話,咱們能夠在一開始只進行標記,並不實際刪除,等到Compaction的時候,咱們再進行實際刪除。 總之一句話,基於log-structured的存儲引擎,咱們能夠經過後臺的Compaction來實現update和delete,Compaction時依然能夠進行數據的寫入和查詢。架構
至此,每一個Segment文件都在內存中有了對應的Hash table。 咱們查詢時爲了找到特定Key對應的Value,咱們依次查詢每一個Segment文件便可,查詢每一個Segment文件的過程和以前同樣。
這種Append-only Log-structured的存儲引擎的優勢:
爲何再也不對Segment文件作索引呢?
這樣咱們就不須要順序遍歷每一個Segment文件了,有了索引咱們就只須要訪問包含特定Key的Segment文件。
如今對Segment文件的格式作個簡單的改變:咱們要求全部的 key-value對必須按照Key排序。 這種格式咱們稱之爲Sorted String Table, 簡稱爲SSTable。 咱們也要求在每一個已經Merged的Segment文件中1個Key只會出現一次,Compaction過程保證了這一點。
SSTable相比Log Segments + Hash Indexes 有如下幾個明顯的優點:
那麼咱們如何保證Segment文件有序呢? 由於數據寫入通常都要通過內存,在內存中咱們能夠利用Red-black tree 或者AVL tree保證有序。
至此,咱們基於SSTable的存儲引擎能夠這樣Run起來:
至此,LSM-Tree(Log-Structured Merge-Tree)的3個組件:SSTable,Memtable,Write-Ahead-Log終於全了。 從開始最簡單的Key-Value 數據庫 講到如今,我相信你已經理解了LSM-Tree的核心思想。
LSM-Tree 已經被普遍使用,好比LevelDB,RocksDB,Cassandra,HBase等,其中的SSTable也是被普遍借鑑,好比ClickHouse,Palo等。
如圖,一個磁盤由多個盤片組成。
如圖,1個盤片由一個個的同心圓組成,一個同心圓就是一個磁道,每一個磁道由多個扇區組成,每一個磁道的扇區數量是一個常量,每一個扇區的大小通常是4KB,扇區是磁盤基本的物理單元。
一次磁盤IO的耗時主要由三部分組成:尋道時間 + 旋轉延遲 + 數據傳輸時間。
提升磁盤讀寫速度方法就是儘可能減少尋道時間和旋轉延遲,而減小尋道時間和旋轉延遲的方法就是減小磁盤的隨機IO,這就是爲何磁盤順序讀寫的性能遠高於隨機讀寫的緣由。
前面咱們從零開始瞭解了LSM-Tree的核心原理,可是在數據庫領域使用最普遍的索引結構是B-tree及其變種。
其實以前咱們爲最簡單的數據庫增長索引的時候,若是咱們同時但願提升查詢性能,支持原地更新和刪除,支持Point query和Scan query, 保持高效的插入性能,咱們就會比較天然的想到二叉查找樹, 平衡二叉查找樹,紅黑樹,B-Tree 及其最多見的變種B+Tree等樹結構, 若是再考慮到面向磁盤,以及更好地支持Scan query,咱們就會選擇B+Tree。B+Tree具備較低的深度,這樣就減小了磁盤 Seek操做的次數。
相似LSM-Tree,B-Tree也能夠提供高效地Point query和Scan query。 可是二者的設計哲學是徹底不一樣的:LSM-Tree是將數據拆分爲幾百M大小的Segments,並是順序寫入;B-Tree則是面向磁盤,將數據拆分爲固定大小的Block或Page, 通常是4KB大小,和磁盤一個扇區的大小對應,Page是讀寫的最小單位。
在數據的更新和刪除方面,B-Tree能夠作到原地更新和刪除,但因爲LSM-Tree只能追加寫,因此只能在Segment Compaction的時候進行真正地更新和刪除。
你們能夠經過B+Tree 可視化理解B+Tree的插入,查找,更新和刪除過程。
關於B+Tree更詳細的原理能夠參考此文MySQL索引背後的數據結構及算法原理。
通常而言, LSM-tree的寫更加高效(追加順序寫),B-tree的讀更加高效(LSM-tree須要訪問幾個不一樣的數據結構)。
LSM-Tree的優勢:
LSM-Tree的缺點:
[1] InnoDB事務及索引原理
[2] MySQL B+樹索引和哈希索引的區別
http://blogread.cn/it/article/7630?f=wb_blogread
[3] 十問 TiDB :關於架構設計的一些思考
https://mp.weixin.qq.com/s/m2_Mf0-x_KpPHbnOawyy2A
[4] 黃東旭:TiDB 數據庫的四大應用場景分析
https://mp.weixin.qq.com/s/t8SA4tlfTjJ77CRynbRm-Q
[5] SnappyData 原理和架構: Streaming Processing,OLTP,OLAP
https://blog.bcmeng.com/post/snappydata.html
[6] 架構選型之痛,如何構造 HTAP 數據庫來收斂技術棧?
https://mp.weixin.qq.com/s/ivJCEXmstbVAdSVgIfv54Q
[7] 暢想TiDB應用場景和HTAP演進之路
https://blog.bcmeng.com/post/tidb-application-htap.html
[8] 數據庫從0到0.1 (二): OLTP VS OLAP VS HTAP
https://blog.bcmeng.com/post/oltp-olap-htap.html
[9] CS_Offer/DataStructure/README.md
https://github.com/xuelangZF/CS_Offer/blob/master/DataStructure/README.md
[10] SkipList的那點事兒
https://sylvanassun.github.io/2017/12/31/2017-12-31-skip_list/
[11] key / value 數據庫的選型