本文是左耳耗子推薦的Medium上的一篇關於MySQL的文章Some study on database storage internals,本人覺得文章十分好,就取得了作者的許可,自行進行了翻譯,原文連結見文末。本文是一篇介紹性的文字,所以文中並沒有對一些概念進行詳細介紹,比如說Sorted Strings Table結構和Bloom filters演算法等專業概念,感興趣的小夥伴可以學習參考中給出的連結或持續關注本人後續文章。
我一直以來都在不斷的研究和探索資料庫的內部儲存原理。我認為這個話題是非常巨大且複雜的,我努力所學也只佔其千萬分之一。在這篇文章中,我將會講解一些資料庫儲存的內部機制,資料庫是如何進行優化操作來提供驚人速度及其優勢和缺點。
當我們談起資料庫內部儲存結構時,人們都會想到B樹或者B+樹,但是我們在這裡並不會談論這些資料結構的原理,我們會展示這些資料結構為什麼適合作為資料庫儲存的內部結構以及使用這些資料結構的目的。
傳統的關係型資料將資料以B樹的形式儲存在磁碟上,它們也會在RAM上使用B樹維護這些資料的索引,來保證更快的訪問速度。插入的行儲存在B樹的葉子節點上,所有的中間節點用來儲存用於導航查詢語句的原資料。因此,當有數以百萬計的資料被插入到資料庫中時,索引和資料儲存會變得十分大。因此,為了快速的訪問,需要從磁碟中載入所有資料到記憶體,但是RAM一般沒有這麼大的空間來儲存所有的資料。因此,資料庫必須從磁碟中讀取部分資料。這種載入資料的場景如下圖所示:
磁碟I/O花費的時間很長,是影響資料庫效能的主要原因之一。B樹是支援隨機讀寫,in-place 替換,十分緊湊並且自平衡的資料結構,但是受磁碟I/O速度的限制。隨機讀意味著當訪問磁碟資料時,磁頭必須移動到柱面上的指定位置,因此會消耗大量時間。
B樹被設計為使用block的形式儲存資料,因為作業系統讀取讀取一個block的資料要比讀取單獨位元組資料要快的多。MySQL的InnoDB儲存引擎的block大小為16KB。這意味著每次你讀取或者寫入資料時,大小為16KB的block資料會被從磁碟載入到RAM中,它會被寫入新的資料,並且再次寫回到磁碟上。假設資料庫表的每一行資料為128位元組(實際大小會變化),一個block(葉子節點)為16KB,儲存了(16 * 1024) / 128 = 128行資料。B樹的高度一般小於10,但是每一層的節點數量卻很多,由此可以管理數以萬計的資料。基於上述特性,B樹適合作為資料內部儲存結構。
因此,在B樹上進行讀操作是相對來說比較快速的,因為該操作只需要遍歷一些節點並且進行較少次數的磁碟I/O請求。而且,範圍查詢因為可以將資料以block的形式進行獲取和操作而速度更快。你可以進行下列操作來讓基於B-Tree的資料庫效能更好:
- 減少索引節點數量:這是提升關係型資料庫效能的常用策略。索引越多,插入和更新操作需要管理的索引數量也越多。當資料庫資料執行時間越來越久時,就需要刪除一些老舊或者無用的索引,並且謹慎地新增新的索引。但是你也要注意,索引越少意味著查詢效能越差,你需要在查詢效能和插入更新效能之間進行取捨(譯者注:可以考慮資料庫的讀寫比率)。
- 順序插入:如果你能以主鍵大小為基礎進行大量資料的順序插入,那麼插入資料的速度會十分的快。因為在插入過程中,插入行所屬的block已經在記憶體中,所以資料庫可以直接將行插入到記憶體的資料結構中,然後通過一次磁碟I/O提交到數磁碟中。當然,這些都取決於資料庫的具體實現,但是我認為現代的資料庫一般都會進行類似的優化。
但是B樹並不是適合所有情景的最優儲存結構。對B樹結構的寫操作效能很差,比隨機讀還要差,因為資料庫必須從磁碟中載入資料對應的頁,然後修改它並沖洗新寫入到磁碟中。隨機寫入時平均有100位元組每秒寫入速度,這個限制是由於磁碟的基本工作原理。事實上,簡單依賴於快取的使用,索引搜尋和更多的記憶體可以處理更多的讀操作,但是應付更多的寫操作就比較麻煩。當你需要寫入或更新大量的資料時,B樹結構並不是最正確的選擇。長久以來,傳統資料庫進行了大量的優化,比如說InnoDB嘗試使用緩衝來減少磁碟I/O操作。具體操作如下所示:
資料庫寫操作可以通過提升磁碟的頻寬來提升速度,但是目前關係型資料庫都沒有這樣做。而且關係型資料庫管理系統一般都是十分複雜的,因為他們使用鎖,併發,ACID事務等操作,這使得寫入操作更加複雜。
當今資訊時代,在比如訊息、聊天、實時通訊和物聯網等客戶為中心的服務和大量無結構化資料的分散式系統中,每小時都會進行數百萬計的寫入操作。因此,這些系統是以寫為主的系統,為了迎合這些系統的需要,資料庫需要能夠擁有快速插入資料的能力。典型的資料庫並不能很好的滿足類似的場景,因為它們無法應付高可用性,儘可能的最終一致性,無格式資料的靈活性和低延遲等要求。
LSM樹(Log Structured Merge Tree)應運而生。LSM並不是一種類似於B樹的資料結構,而是一個系統。在LSM系統中,並沒有對資料的in-place替換;一旦資料被寫入磁碟,它就再也不會被修改。顯然,它是一種只能在末尾新增(append only)的寫入系統。一些日誌結構的檔案系統比如ext3/4也使用類似的原理。因此,該系統就如同順序的記錄資料日誌一般。基本上,LSM系統利用了順序寫的優勢。傳統的磁碟驅動的寫操作最高可以達到100MB/s,現代的固態硬碟在順序寫時的速度則更快。事實上,固態硬碟驅動有一些內建的並行機制來讓它可以同時寫入16到32MB的資料。LSM樹和固態硬碟的特性十分匹配。順序寫要比隨機寫快很多。
為了正確地理解上述場景,讓我們簡單的看一下Facebook的Cassandra資料庫是如何使用LSM原則的。
Cassandra或者任何LSM系統都會維護一個或者多個用來在寫入磁碟前儲存資料的記憶體資料結構(如上圖中的memtable),比如說子平衡樹(AVL)、紅黑樹、B樹或者跳錶。該記憶體資料結構維護一個排序的資料集。不同的LSM實現互使用不同的資料結構來適應不同的需求,並不存在標準的LSM實現。當記憶體中儲存的資料超過配置的閾值時,記憶體中儲存的資料就會被放置在將會被寫入磁碟的佇列中。為了flush資料,Cassandra順序地寫入排序的資料到磁碟中。磁碟維護一個叫做“SSTable”(Sorted Strings Table)的資料結構,該資料就是寫入檔案資料的有序的快照,SSTable是不可變的。LSM系統可以管理磁碟上的多個檔案。
因此,如果資料在記憶體中沒有被發現,Cassandra需要掃描所有磁碟上的SSTables來搜尋該資料。因此,Cassandra的讀操作相對來說要比寫操作慢,但是這裡有一些可以處理的方法。Cassandra或者其他LSM系統會在後臺執行壓縮程式來減少SSTable的數量。壓縮程式對SSTable進行歸併排序,在新的SSTable找那個插入新的排序資料並且刪除老的SSTables。但是使用壓縮程式有時候無法應付資料庫中數以百萬計的更新操作。
因此,一些概率資料結構(probabilistic data structures)比如Bloom filters被應用來快速判斷是否一些資料存在於SSTable。Bloom filters十分適合對記憶體中的資料進行判斷,因為它需要進行大量的隨機查詢來進行資料是否存在的概率性判斷。Bloom filters演算法可以極大地減少遍歷查詢SSTables的花費。因此,LSM系統解決了在大資料中寫操作需要花費大量時間的問題。LSM系統也有Read amplification的問題-會讀取出比它實際需要更多的資料。因此,還有介於B Tree和LSM Tree之間的解決方法來給出我們最優(不一定準確)的讀寫效率嗎?
Fractal Tree Index是基於B-Tree的資料結構。依據開發人員給出的benchmark,該資料結構有比B-Tree更優良的效能。Fractal tree支援在非葉節點上的資訊快取。MySQL的高效能儲存引擎Tokudb就使用了Fractal tree。
如上圖所示,在Fractal Tree中,你進行的新增列,刪除列,插入,更新等任何操作都會被當做操作訊息儲存在非葉節點上。由於操作只是被簡單地儲存在快取或者任何次級索引快取(secondary index buffer)中,所以,所有的操作都會被迅速執行結束。當某一個節點的快取滿了之後,這些操作訊息會依次從根節點,經過非葉節點,向葉節點進行傳遞。葉節點仍然儲存著真實資料。當進行讀時,讀操作會考慮查詢路徑節點上的所有操作訊息來獲取真實的資料狀態。但是由於tokudb會盡力將所有非葉節點快取在記憶體中,所以這一過程也很快。
tokubd中的block最大可以達到4MB,而不是InnoDB中的16KB。這樣的大小可以允許一次I/O操作時載入或寫回更多的資料,這也有助於一次壓縮更多資料來減少磁碟上資料的儲存大小。因此,tokudb強調藉助更大的block大小能夠實現更好的資料壓縮和更少的磁碟I/O。tokudb宣稱它們的儲存引擎比InnoDB更快,提供比InnoDB更快的讀寫吞吐,並且tokudb也宣稱自己有更少的碎片(fragmentation)問題,它也支援多叢集索引等。下圖是benchmark的相關統計圖:
只有你係統中的benchmark可以幫助你判斷正確的資料點和需求解決方案。但是MySQL的儲存引擎會持續地不斷改進和支援新出現的需求。LSM樹是為了高寫入場景的系統,然而B樹是為了傳統的場景應用。Fractal樹的索引改進了B樹索引存在的一些缺陷。因此,未來會不斷地出現技術上的革新,包括資料庫儲存技術,硬體,磁碟驅動和作業系統,讓我們拭目以待。
訂閱最新文章,歡迎關注我的微信公眾號