對於OLTP型數據庫梳理和對於NewSQL的思考和建議

從數據庫底層來看,數據庫到目前爲止,有這幾種數據結構

1.b樹,b+樹,(mysql,PostgreSQL)

2.lsm樹(LevelDB,RocksDB,TIDB,CockroachDB)

3.基於B+樹和LSM樹改良的樹,比如Fractal樹(TokuDB)

 

下面一一介紹每種樹的典型應用

 

B樹,B+樹

傳統的關係型數據將數據以B樹的形式存儲在磁盤上,它們也會在RAM上使用B樹維護這些數據的索引,來保證更快的訪問速度。插入的行存儲在B樹的葉子節點上,所有的中間節點用來存儲用於導航查詢語句的原數據。因此,當有數以百萬計的數據被插入到數據庫中時,索引和數據存儲會變得十分大。因此,爲了快速的訪問,需要從磁盤中加載所有數據到內存,但是RAM一般沒有這麼大的空間來存儲所有的數據。因此,數據庫必須從磁盤中讀取部分數據。這種加載數據的場景如下圖所示:

b+樹

圖片引自https://segmentfault.com/a/1190000015892186​​​​

B樹和B+樹區別在於B樹的內部節點有指向關鍵字具體信息的指針,而b+樹沒有,因此b+樹相對b樹,有如下優點

磁盤讀寫代價更低

B+樹的內部結點並沒有指向關鍵字具體信息的指針。因此其內部結點相對B樹更小。如果把所有同一內部結點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多。一次性讀入內存中的需要查找的關鍵字也就越多。相對來說IO讀寫次數也就降低了。

B+樹的查詢效率更加穩定

由於非終結點並不是最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。所以任何關鍵字的查找必須走一條從根結點到葉子結點的路。所有關鍵字查詢的路徑長度相同,導致每一個數據的查詢效率相當。

 

InnoDB

mysql的InnoDB是大家工作中最常用的數據庫存儲引擎,B+樹,聚簇索引,行級鎖。聚簇索引數據的物理存放與索引都是有序的,它把所有的衛星數據都存儲在葉節點中,內部節點只存放關鍵字和孩子指針。

 

圖片引自https://blog.csdn.net/u010842515/article/details/68929687

InnoDB的所有輔助索引都引用主鍵作爲data域。InnoDB 不會壓縮索引,所以,如果主鍵定義的比較大,其他索引也將很大。如果想在表上定義很多索引,則爭取儘量把主鍵定義得小一些。輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然後用主鍵到主索引中檢索獲得記錄。

圖片引自https://blog.csdn.net/u010842515/article/details/68929687

頁是InnoDB存儲引擎的最小管理單位,每頁大小默認是16KB。以64位操作系統,long型主鍵爲例,一個索引頁可以裝下大概16k/(8+8)=1k個關鍵字,因爲每一級下級索引頁也可以裝下1k個關鍵字,而一級索引指向的二級索引有1k個,因此二級索引就有1k*1k=100萬個關鍵字。如果數據庫一行數據在100字節,那麼每一個數據頁最多可以裝下16*1024/100=160條數據(實際情況是mysql不會把每頁的數據都裝滿,否則再插入一條數據,當前頁就裝不下了,就得在頁間挪動數據了),假如這顆樹只有兩級索引,那麼每相鄰的二級索引指向的葉子節點就有160行數據。那麼這樣一顆二級索引的樹可以指向100萬*160=1.6億數據。

以64位操作系統,36位uuid做主鍵爲例,一個索引頁可以裝下大概16*1024/(36+8)= 372個下級索引,二級索引就有372*372=13萬個關鍵字,如果一行數據在1k字節,那麼每個數據頁可以裝下16行數據,那麼這樣一個二級索引樹可以指向13萬*16=208萬行數據。

綜上innodb的數據和索引都是順序排列的,目前IT業內有mysql超過1000萬行數據查詢速度就慢了的說法,其實就是b+樹索引由2級到3級,需要遍歷的樹層級多了導致的。innodb數據庫設計優化的重點在主鍵索引的長度控制,其次在於控制每行的數據量。設計併發比較高的系統的時候,可以採用冷熱分離的數據庫設計方法,對於熱數據,主鍵採用自增的方式,優先保證數據的插入速度。對於冷數據來說,主鍵可以採用用戶經常批量查詢的字段做主鍵(比如用戶id+熱數據id),可以充分利用聚簇索引的數據連續的特點。儘量減少讀取磁盤的次數。

 

MyISAM

mysql數據庫的MyISAM表也是基於B+Tree的,它的葉子節點上的data,並不是數據本身,而是數據存放的地址。主索引和輔助索引沒啥區別,只是主索引中的key一定得是唯一的。這裏的索引都是非聚簇索引。他的數據的物理地址是凌亂的,拿到這些物理地址,按照合適的算法進行I/O讀取,於是開始不停的尋道不停的旋轉。不過,如果涉及到大數據量的排序、全表掃描、count之類的操作的話,還是MyISAM佔優勢些,因爲索引所佔空間小,這些操作是需要在內存中完成的。MyISAM引擎由於更新的時候採用表鎖的方式,目前多應用在只讀數據的查詢方面。

圖片引自https://blog.csdn.net/u010842515/article/details/68929687

 

LSM樹

傳統數據庫的寫入是有瓶頸的,爲了迎合以寫爲主的系統的需要,數據庫需要能夠擁有快速插入數據的能力。因此LSM樹應運而生。

LSM的原理是有寫入請求的時候, 爲了保護內存中的數據,在磁盤上先記錄logfile(當內存中的數據flush到磁盤上時,就可以拋棄相應的Logfile),然後寫入到內存中(內存沒有尋道速度的問題,隨機寫的性能得到大幅提升),在內存中構建一顆有序小樹,隨着小樹越來越大,內存的小樹會flush到磁盤上。隨着小樹越來越多,讀的性能會越來越差,因此需要在適當的時候,對磁盤中的小樹進行merge,多棵小樹變成一顆大樹。當讀時,優先看內存中有沒有匹配的key,如果有則返回,沒有則遍歷所有的樹(RocksDB,Cassandra增加了Bloom Filter來判斷請求的key是否可能在樹中),但在每顆小樹內部數據是有序的。

圖片引自https://www.cnblogs.com/yanghuahui/p/3483754.html

 

LevelDB,RocksDB

LevelDB 是由 Google 開發的單機版 key-value存儲系統,是基於 LSM(Log-Structured-Merge Tree) 的典型實現,RocksDB是Facebook基於LevelDB優化的一種嵌入式單機版Key-value存儲系統,該數據庫能夠充分利用閃存的性能,大大提升應用服務器的速度,在其上基於一致性協議可以構建複雜的系統,比如TIDB,CockroachDB。

 

Fractal樹

Fractal樹是一種寫優化的磁盤索引數據結構,集合了LSM寫入速度快的優點和b+樹讀取快的優點。它索引維護了類似b+樹的結構,利用索引節點的MessageBuffer緩存更新操作,充分利用數據局部性原理, 將隨機寫轉換爲順序寫,這樣極大的提高了隨機寫的效率。從理論複雜度和測試性能兩個角度上看, Fractal樹的Insert/Delete/Update操作性能優於B+樹。 但是讀操作性能低於B+樹。Fractal樹支持在做DDL操作的同時(例如添加索引),用戶依然可以執行寫入操作, 這個特點是Fractal樹樹形結構天然支持的。Tokutek研發團隊的iiBench測試結果顯示: TokuDB(採用Fractal樹)的insert操作(隨機寫)的性能比InnoDB快很多(每次插入唯一主鍵的時候,還是要查詢的,因此官方的測試結果存疑),而Select操作(隨機讀)的性能低於InnoDB的性能,但是差距較小

在Fractal樹中,進行的添加列,刪除列,插入,更新等任何操作都會被當做操作消息存儲在非葉節點上。由於操作只是被簡單地存儲在緩存或者任何次級索引緩存(secondary index buffer)中,所有的操作都會被迅速執行結束。當某一個節點的緩存滿了之後,這些操作消息會依次從根節點,經過非葉節點,向葉節點進行傳遞。葉節點仍然存儲着真實數據。當進行讀時,讀操作會考慮查詢路徑節點上的所有操作消息來獲取真實的數據狀態。但是由於tokudb會盡力將所有非葉節點緩存在內存中,所以這一過程也很快。只是分形樹的範圍查詢基本等價於進行N次單條數據查詢操作,因此效率比b+樹低很多

在線更新表結構實現也是基於消息來實現的, 但和Insert/Delete/Update操作不同的是, 前者的消息下推方式是廣播式下推(父節點的一條消息,應用到所有的兒子節點), 後者的消息下推方式單播式下推(父節點的一條消息,應用到對應鍵值區間的兒子節點), 實現類似於Insert操作。

 

圖片引自https://segmentfault.com/a/1190000015892186

 

 

NewSQL

單機數據庫滿足不了互聯網線上數據規模,google Spanner/F1論文的發表,給大家指明瞭一個新的方向。出現了NewSQL這個概念,NewSQL是指這樣一類新式的關係型數據庫管理系統,針對OLTP(讀-寫)工作負載,追求提供和NoSQL系統相同的擴展性能,且仍然保持ACID和SQL等特性,衝破CAP的枷鎖,在三者之間完美平衡,開源領域以TIDB,CockroachDB爲代表

 

CAP理論:一個分佈式系統最多隻能同時滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance)這三項中的兩項。

圖片引自http://www.hollischuang.com/archives/666

 

TIDB

TiDB 是 PingCAP 公司受 Google Spanner / F1 論文啓發而設計的開源分佈式 HTAP (Hybrid Transactional and Analytical Processing) 數據庫,結合了傳統的 RDBMS 和 NoSQL 的最佳特性。TiDB 兼容 MySQL,支持無限的水平擴展,具備強一致性和高可用性。一個 TiDB 集羣擁有幾個 TiDB 服務、幾個 TiKV 服務和一組 Placement Deiver(PD)(通常 3-5 個節點)。TiDB 服務是無狀態 SQL 層,TiKV 服務是鍵值對存儲層,PD 則是管理組件,從頂層視角負責存儲元數據以及負載均衡,TiDB 可以進行在線 DDL

圖片引自https://www.oschina.net/translate/how-to-do-performance-tuning-on-tidb

假設我們使用如下 SQL 來插入一條數據到表 t

INSERT INTO t(id, name, address) values(1, “Jack”, “Sunnyvale”);

執行流程如下圖

圖片引自https://www.oschina.net/translate/how-to-do-performance-tuning-on-tidb

TiDB 只存儲鍵值對,那麼要如何實現諸如數據庫、表和索引等高層概念呢?在 TiDB 中,每個表都有一個關聯的全局唯一編號,被稱爲 “table-id”。特定表中的所有數據(包括記錄和索引)的鍵都是以 8 字節的 table-id 開頭的。對於每行表數據,它的 key 是一個 64 位整數,稱爲 Handle ID。如果一張表存在 int 類型的主鍵,會把主鍵的值當作表數據的 Handle ID,否則由系統自動生成 Handle ID。表數據的 value 由這一行的所有數據編碼而成。在讀取表數據的時候,可以按照 Handle ID 遞增的順序返回。

TiDB 的索引數據和表數據一樣,也存放在 TiKV 中。每個索引都有一個名爲 “index-id” 的表範圍的唯一編號。它的 key 是由索引列編碼的有序 bytes,value 是這一行索引數據對應的 Handle ID,通過 Handle ID 我們可以讀取這一行的非索引列。

在 TiDB 中,Region 表示一個連續的、左閉右開的鍵值範圍 [start_key,end_key)。每個 Region 有多個副本,並且每個副本稱爲一個 peer 。每個 Region 也歸屬於單獨的 Raft 組,以確保所有 peer 之間的數據一致性。同一表的臨近記錄很可能位於同一 Region 中。當集羣第一次初始化時,只存在一個 Region 。當 Region 達到特定大小(當前默認值爲96MB)時, Region 將動態分割爲兩個鄰近的 Region ,並自動將數據分佈到系統中以提供水平擴展。

 

總結

研究了這麼多數據庫,我最關係的問題是,在一個高頻訪問的系統中(比如騰訊的微信或阿里的淘寶),是直接採用newSQL的數據庫,還是採用基於分庫分表的方案搭建起一個龐大的數據庫羣。或者說,目前的NewSQL還需要做什麼。

1.目前的NewSQL底層基於RocksDB,這是一個寫優化的key-value數據庫,但是對於讀效率沒有b+樹高。但是對於大多數線上應用來說,讀遠大於寫。這個可以加大內存容量,讓內存緩存更多的數據,優化RocksDB參數,採用SSD固態硬盤,讓小文件儘可能快合併,部署更多的數據庫來解決。我挺好奇的,爲什麼TIDB一開始沒有選Fractal樹做key-value底層,我覺得應該是專利技術限制了,以後percona公司看見TIDB做得不錯了,就可以直接將底層換掉,推廣產品,做服務賺錢了。

2.TIDB數據庫內部的數據是有序排列的,如果採用自增整數做主鍵,那麼就會有熱點的問題。如果優化爲採用隨機整數,比如6位機房號(可以支持64個機房,對於一般互聯網公司來說夠了)+19位遞增整數(多臺redis計數器,取自增整數的後幾位+每臺redis的固定編號,每秒鐘每個機房可以支持524288個不重的id)+39位時間戳(內網機器之間採用NTP同步時間,機器時間精確到秒,可以用17433年,佛說末法10000年,足夠了),TIDB就不會有插入熱點數據的問題了,但是在這種情況下,RocksDB也需要先查詢一次這個id有沒有插入,還得檢索一次了,查詢還是硬傷。

3.相關聯的數據批量查詢的問題,得想辦法讓想關聯的數據id是相近的,並且不重,這個在64位rowId限制的況下,我暫時沒有想出來。主鍵不是64位整數,數據庫就會生成一個隨機的主鍵(防止出現單點過熱),查詢的時候多一次根據主鍵查詢數據的rowId,同時數據也是隨機分佈的。如果改成變長rowId,對於整型的聯合主鍵直接用整數連接起來就好了,那麼就不是問題了,既兼容了原來的數據,還可以讓用戶自由定義聯合主鍵,讓相關聯的數據放在一起。同時在實現上也比較簡單,rowId後面的時間戳是定長的,很容易算出來rowId的長度。

4.對於一個複雜的事務來說,由於涉及的表衆多,他們大概率不在一個同一個實體機上,再加上TIDB正常需要3份拷貝(爲了保證儘可能不會因爲某一臺機器down機導致數據丟失,PD就會儘可能把數據分散),因此TIDB需要通知的TIKV就更多了,機器之間的通信時間也是比單臺機器多了好多。TIDB官網數據,在 10 節點內,TiDB 寫入能力(Insert TPS)和節點數量對比MySQL 基本成 40% 線性遞增,在複雜事務中,這個記錄應該更差。官網沒有說明是對比的單機的mysql,還是多主強同步的mysql集羣。官網沒有寫10節點以上的情況,這個場景下節點的增多會造成網絡io比較大,可能成爲系統的瓶頸。但是分庫分表的方式,每個應用服務器只和自己相關聯的數據庫打交道,網絡io是可控的。

5.TiDB支持數據導出功能,通過一個叫作 Pump 的小程序,彙總寫入到 Kafka 集羣。在下游有一個叫 Drainer 的組件來消費 Kafka 的數據,按照事務的順序還原成 SQL,同步到下游數據庫。TiDB支持多源多目的地的數據同步,通過Wormhole的工具實現。PingCAP 也在研發自己的 OLAP 的存儲引擎,應該是列式存儲引擎,如果能推出來,那麼對於實時分析數據來說就比較方便了

 

跳出NewSQL的思維方式,我不用重寫數據庫,每一對機器配置成雙主,保證數據庫不出現單點故障,結合mycat和otter,在mysql之上構建一個數據分佈和調度的上層,不追求跨數據庫事物一致,追求數據庫的統一動態編輯,統一遷移,不同區域數據庫之間的自動同步,整體上類似TiDB,可不可行呢。首先對於這樣的系統,跨數據庫的事務一致性不好解決,增加了應用的複雜度,其次統一的索引也需要應用程序自己維護,唯一的好處就是控制了網絡流量的複雜度

 

綜上,如果NewSQL採用變長rowId後,高併發應用真的可以使用了。目前還是用mysql集羣提供OLTP服務,同步到OLAP服務器上(ClickHouse,Palo),進行實時分析數據比較划算。

 

 

參考文檔

https://segmentfault.com/a/1190000015892186

https://www.jianshu.com/p/1ed61b4cca12

http://www.cnblogs.com/yanghuahui/p/3483047.html

https://blog.csdn.net/u010842515/article/details/68929687

https://www.jianshu.com/p/6636e4671f83

https://www.jianshu.com/p/6f68d3c118d6

https://www.jianshu.com/p/3832ae37fac4

https://blog.csdn.net/doc_sgl/article/details/51068131

https://www.oschina.net/translate/how-to-do-performance-tuning-on-tidb

 

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