mysql delete 会锁表吗_MySQL高压缩引擎TokuDB 揭秘
原文出處:http://mysql.taobao.org/monthly/2017/07/04/
HybridDB for MySQL(原名petadata)是面向在線事務(wù)(OLTP)和在線分析(OLAP)混合場景的關(guān)系型數(shù)據(jù)庫。HybridDB采用一份數(shù)據(jù)存儲來進(jìn)行OLTP和OLAP處理,解決了以往需要把一份數(shù)據(jù)多次復(fù)制來分別進(jìn)行業(yè)務(wù)交易和數(shù)據(jù)分析的問題,極大地降低了數(shù)據(jù)存儲的成本,縮短了數(shù)據(jù)分析的延遲,使得實時分析決策稱為可能。
HybridDB for MySQL兼容MySQL的語法及函數(shù),并且增加了對Oracle常用分析函數(shù)的支持,100%完全兼容TPC-H和TPC-DS測試標(biāo)準(zhǔn),從而降低了用戶的開發(fā)、遷移和維護(hù)成本。
TokuDB是TokuTek公司(已被 Percona收購)研發(fā)的新引擎,支持事務(wù)/MVCC,有著出色的數(shù)據(jù)壓縮功能,支持異步寫入數(shù)據(jù)功能。
TokuDB索引結(jié)構(gòu)采用fractal tree數(shù)據(jù)結(jié)構(gòu),是buffer tree的變種,寫入性能優(yōu)異,適合寫多讀少的場景。除此之外,TokuDB還支持在線加減字段,在線創(chuàng)建索引,鎖表時間很短。
Percona Server和Mariadb支持TokuDB作為大數(shù)據(jù)場景下的引擎,目前官方MySQL還不支持TokuDB。ApsaraDB for MySQL從2015年4月開始支持TokuDB,在大數(shù)據(jù)或者高并發(fā)寫入場景下推薦使用。
TokuDB優(yōu)勢
數(shù)據(jù)壓縮
TokuDB最顯著的優(yōu)勢就是數(shù)據(jù)壓縮,支持多種壓縮算法,用戶可按照實際的資源消耗修改壓縮算法,生產(chǎn)環(huán)境下推薦使用zstd,實測的壓縮比是4:1。
目前HybridDB for MySQL支持6中壓縮算法:
- lzma: 壓縮比最高,資源消耗高
- zlib:Percona默認(rèn)壓縮算法,最流行,壓縮比和資源消耗適中
- quicklz:速度快,壓縮比最低
- snappy:google研發(fā)的,壓縮比較低,速度快
- zstd:壓縮比接近zlib,速度快
- uncompressed:不壓縮,速度最快
Percona建議6核以下場景使用默認(rèn)壓縮算法zlib,6核以上可以使用壓縮率更高的壓縮算法,大數(shù)據(jù)場景下推薦使用zstd壓縮算法,壓縮比高,壓縮和解壓速度快,也比較穩(wěn)定。
用戶可以在建表時使用ROW_FORMAT子句指定壓縮算法,也可用使用ALTER TABLE修改壓縮算法。ALTER TABLE執(zhí)行后新數(shù)據(jù)使用新的壓縮算法,老數(shù)據(jù)仍是老的壓縮格式。
mysql> CREATE TABLE t_test (column_a INT NOT NULL PRIMARY KEY, column_b INT NOT NULL) ENGINE=TokuDB ROW_FORMAT=tokudb_zstd;mysql> SHOW CREATE TABLE t_testG Table: t_testCreate Table: CREATE TABLE `t_test` ( `column_a` int(11) NOT NULL, `column_b` int(11) NOT NULL, PRIMARY KEY (`column_a`)) ENGINE=TokuDB DEFAULT CHARSET=latin1 ROW_FORMAT=TOKUDB_ZSTDmysql> ALTER TABLE t_test ROW_FORMAT=tokudb_snappy;mysql> SHOW CREATE TABLE t_testG Table: t_testCreate Table: CREATE TABLE `t_test` ( `column_a` int(11) NOT NULL, `column_b` int(11) NOT NULL, PRIMARY KEY (`column_a`)) ENGINE=TokuDB DEFAULT CHARSET=latin1 ROW_FORMAT=TOKUDB_SNAPPYTokuDB采用塊級壓縮,每個塊大小是4M,這是壓縮前的大小;假設(shè)壓縮比是4:1,壓縮后大小是1M左右。比較tricky地方是:TokuDB壓縮單位是partition,大小是64K。相比innodb16K的塊大小來說要大不少,更有利壓縮算法尋找重復(fù)串。
上面提到,修改壓縮算法后新老壓縮格式的數(shù)據(jù)可以同時存在。如何識別呢?
每個數(shù)據(jù)塊在壓縮數(shù)據(jù)前預(yù)留一個字節(jié)存儲壓縮算法。從磁盤讀數(shù)據(jù)后,會根據(jù)那個字節(jié)的內(nèi)容調(diào)用相應(yīng)的解壓縮算法。
另外,TokuDB還支持并行壓縮,數(shù)據(jù)塊包含的多個partition可以利用線程池并行進(jìn)行壓縮和序列化工作,極大加速了數(shù)據(jù)寫盤速度,這個功能在數(shù)據(jù)批量導(dǎo)入(import)情況下開啟。
在線增減字段
TokuDB還支持在輕微阻塞DML情況下,增加或刪除表中的字段或者擴(kuò)展字段長度。
執(zhí)行在線增減字段時表會鎖一小段時間,一般是秒級鎖表。鎖表時間短得益于fractal tree的實現(xiàn)。TokuDB會把這些操作放到后臺去做,具體實現(xiàn)是:往root塊推送一個廣播msg,通過逐層apply這個廣播msg實現(xiàn)增減字段的操作。
需要注意的:
- 不建議一次更新多個字段
- 刪除的字段是索引的一部分會鎖表,鎖表時間跟數(shù)據(jù)量成正比
- 縮短字段長度會鎖表,鎖表時間跟數(shù)據(jù)量成正比
穩(wěn)定高效寫入性能
TokuDB索引采用fractal tree結(jié)構(gòu),索引修改工作由后臺線程異步完成。TokuDB會把每個索引更新轉(zhuǎn)化成一個msg,在server層上下文只把msg加到root(或者某個internal)塊msg buffer中便可返回;msg應(yīng)用到leaf塊的工作是由后臺線程完成的,此后臺線程被稱作cleaner,負(fù)責(zé)逐級apply msg直至leaf塊
DML語句被轉(zhuǎn)化成FTINSERT/FTDELETE,此類msg只應(yīng)用到leaf節(jié)點。
在線加索引/在線加字段被轉(zhuǎn)化成廣播msg,此類msg會被應(yīng)用到每個數(shù)據(jù)塊的每個數(shù)據(jù)項。
實際上,fractal tree是buffer tree的變種,在索引塊內(nèi)緩存更新操作,把隨機(jī)請求轉(zhuǎn)化成順序請求,縮短server線程上下文的訪問路徑,縮短RT。所以,TokuDB在高并發(fā)大數(shù)據(jù)量場景下,可以提供穩(wěn)定高效的寫入性能。
除此之外,TokuDB實現(xiàn)了bulk fetch優(yōu)化,range query性能也是不錯的。
在線增加索引
TokuDB支持在線加索引不阻塞更新語句 (insert, update, delete) 的執(zhí)行。可以通過變量 tokudbcreateindex_online 來控制是否開啟該特性, 不過遺憾的是目前只能通過 CREATE INDEX 語法實現(xiàn)在線創(chuàng)建;如果用ALTER TABLE創(chuàng)建索引還是會鎖表的。
mysql> SHOW CREATE TABLE t_testG Table: t_testCreate Table: CREATE TABLE `t_test` ( `column_a` int(11) NOT NULL, `column_b` int(11) NOT NULL, PRIMARY KEY (`column_a`)) ENGINE=TokuDB DEFAULT CHARSET=latin1 ROW_FORMAT=TOKUDB_SNAPPYmysql> SET GLOBAL tokudb_create_index_online=ON;mysql> CREATE INDEX ind_1 ON t_test(column_b);mysql> SHOW CREATE TABLE t_testG Table: t_testCreate Table: CREATE TABLE `t_test` ( `column_a` int(11) NOT NULL, `column_b` int(11) NOT NULL, PRIMARY KEY (`column_a`), KEY `ind_1` (`column_b`)) ENGINE=TokuDB DEFAULT CHARSET=latin1 ROW_FORMAT=TOKUDB_SNAPPY寫過程
如果不考慮unique constraint檢查,TokuDB寫是異步完成的。每個寫請求被轉(zhuǎn)化成FT_insert類型的msg,記錄著要寫入的 和事務(wù)信息用于跟蹤。
Server上下文的寫路徑很短,只要把寫請求對應(yīng)的msg追加到roo數(shù)據(jù)塊的msg buffer即可,這是LSM數(shù)據(jù)結(jié)構(gòu)的核心思想,把隨機(jī)寫轉(zhuǎn)換成順序?qū)?#xff0c;LevelDB和RocksDB也是采用類似實現(xiàn)。
由于大家都在root數(shù)據(jù)塊緩存msg,必然造成root塊成為熱點,也就是性能瓶頸。
為了解決這個問題,TokuDB提出promotion概念,從root數(shù)據(jù)塊開始至多往下看2層。如果當(dāng)前塊數(shù)據(jù)塊是中間塊并且msg buffer是空的,就跳過這層,把msg緩存到下一層中間塊。
下面我們舉例說明write過程。
假設(shè),insert之qiafractal tree狀態(tài)如下圖所示:
- insert 300
root數(shù)據(jù)塊上300對應(yīng)的msg buffer為空,需要進(jìn)行inject promotion,也就是說會把msg存儲到下面的子樹上。下一級數(shù)據(jù)塊上300對應(yīng)的msg buffer非空(msg:291),不會繼續(xù)promotion,msg被存儲到當(dāng)前的msg buffer。
- insert 100
root數(shù)據(jù)塊上100對應(yīng)的msg buffer為空,需要進(jìn)行inject promotion,也就是說會把msg存儲到下面的子樹上。下一級數(shù)據(jù)塊上100對應(yīng)的msg buffer也為空,需要繼續(xù)promotion。再下一級數(shù)據(jù)塊上100對應(yīng)的msg buffer非空(msg:84),不會繼續(xù)promotion,msg被存儲到當(dāng)前的msg buffer。
- insert 211
root數(shù)據(jù)塊上211對應(yīng)的msg buffer為空,需要進(jìn)行inject promotion,也就是說會把msg存儲到下面的子樹上。下一級數(shù)據(jù)塊上211對應(yīng)的msg buffer也為空,需要繼續(xù)promotion。再下一級數(shù)據(jù)塊上211對應(yīng)的msg buffer也為空,但是不會繼續(xù)promotion,msg被存儲到當(dāng)前的msg buffer。這是因為promotion至多向下看2層,這么做是為了避免dirty的數(shù)據(jù)塊數(shù)量太多,減少checkpoint刷臟的壓力。
行級鎖
TokuDB提供行級鎖處理并發(fā)讀寫數(shù)據(jù)。
所有的INSERT、DELETE或者SELECT FOR UPDATE語句在修改索引數(shù)據(jù)結(jié)構(gòu)fractal tree之前,需要先拿記錄(也就是key)對應(yīng)的行鎖,獲取鎖之后再去更新索引。與InnoDB行鎖實現(xiàn)不同,InnoDB是鎖記錄數(shù)據(jù)結(jié)構(gòu)的一個bit。
由此可見,TokuDB行鎖實現(xiàn)導(dǎo)致一些性能問題,不適合大量并發(fā)更新的場景。
為了緩解行鎖等待問題,TokuDB提供了行鎖timeout參數(shù)(缺省是4秒),等待超時會返回失敗。這種處理有助于減少deadlock發(fā)生。
讀過程
由于中間數(shù)據(jù)塊(internal block)會緩存更新操作的msg,讀數(shù)據(jù)時需要先把上層msg buffer中的msg apply到葉子數(shù)據(jù)塊(leaf block)上,然后再去leaf上把數(shù)據(jù)讀上來。
3,4,5,6,7,8,9是中間數(shù)據(jù)塊,10,11,12,13,14,15,16,17是葉子數(shù)據(jù)塊;
上圖中,每個中間數(shù)據(jù)塊的fanout是2,表示至多有2個下一級數(shù)據(jù)塊;中間節(jié)點的msg buffer用來緩存下一級數(shù)據(jù)塊的msg,橘黃色表示有數(shù)據(jù),黃綠色表示msg buffer是空的。
如果需要讀block11的數(shù)據(jù),需要先把數(shù)據(jù)塊3和數(shù)據(jù)塊6中的msg apply到葉子數(shù)據(jù)塊11,然后去11上讀數(shù)據(jù)。
Msg apply的過程也叫合并(merge),所有基于LSM原理的k-v引擎(比方LevelDB,RocksDB)讀數(shù)據(jù)時都要先做merge,然后去相應(yīng)的數(shù)據(jù)塊上讀數(shù)據(jù)。
讀合并
如上圖所示,綠色是中間數(shù)據(jù)塊,紫色是葉數(shù)據(jù)塊;中間數(shù)據(jù)塊旁邊的黃色矩形是msg buffer。
如要要query區(qū)間[5-18]的數(shù)據(jù)
- 以5作為search key從root到leaf搜索>=5的數(shù)據(jù),每個數(shù)據(jù)塊內(nèi)部做binary search,最終定位到第一個leaf塊。讀數(shù)據(jù)之前,判斷第一個leaf塊所包含的[5,9]區(qū)間存在需要apply的msg(上圖中是6,7,8),需要先做msg apply然后讀取數(shù)據(jù)(5,6,7,8,9);
- 第一個leaf塊讀取完畢,以9作為search key從root到leaf搜索>9的數(shù)據(jù),每個數(shù)據(jù)塊內(nèi)部做binary search,最終定位到第二個leaf塊。讀數(shù)據(jù)之前,判斷第二個leaf塊所包含的[10,16]區(qū)間存在需要apply的msg(上圖中是15),需要先做msg apply然后讀取數(shù)據(jù)(10,12,15,16);
- 第二個leaf塊讀取完畢,以16作為search key從root到leaf搜索>16的數(shù)據(jù),每個數(shù)據(jù)塊內(nèi)部做binary search,最終定位到第三個leaf塊。第三個數(shù)據(jù)塊所包含的[17,18]區(qū)間不存在需要apply的msg,直接讀取數(shù)據(jù)(17,18)。
優(yōu)化range query
為了減少merge代價,TokuDB提供bulk fetch功能:每個basement node大小64K(這個是數(shù)據(jù)壓縮解壓縮的單位)只要做一次merge操作;并且TokuDB的cursor支持批量讀,一個batch內(nèi)讀取若干行數(shù)據(jù)緩存在內(nèi)存,之后每個handler::indexnext先去緩存里取下一行數(shù)據(jù),只有當(dāng)緩存數(shù)據(jù)全部被消費過之后發(fā)起下一個batch讀,再之后handler::indexnext操作還是先去緩存里取下一行數(shù)據(jù)。
Batch讀過程由cursor的callback驅(qū)動,直接把數(shù)據(jù)存到TokuDB handler的buffer中,不僅減少了merge次數(shù),也減少了handler::index_next調(diào)用棧深度。
異步合并
TokuDB支持后臺異步合并msg,把中間數(shù)據(jù)塊中緩存的msg逐層向下刷,直至leaf數(shù)據(jù)塊。
這過程是由周期運行的cleaner線程完成的,cleaner線程每秒被喚醒一次。每次執(zhí)行掃描一定數(shù)目的數(shù)據(jù)塊,尋找緩存msg最多的中間數(shù)據(jù)塊;掃描結(jié)束后,把msg buffer中的msg刷到(merge)下一層數(shù)據(jù)塊中。
前面提到,大部分寫數(shù)據(jù)并不會把msg直接寫到leaf,而是把msg緩存到root或者某一級中間數(shù)據(jù)塊上。雖然promotion緩解了root塊熱點問題,局部熱點問題依然存在。
假設(shè)某一個時間段大量并發(fā)更新某范圍的索引數(shù)據(jù),msg buffer短時間內(nèi)堆積大量msg;由于cleaner線程是單線程順序掃描,很可能來不及處理熱點數(shù)據(jù)塊,導(dǎo)致熱點數(shù)據(jù)msg堆積,并且數(shù)據(jù)塊讀寫鎖爭搶現(xiàn)象越來越嚴(yán)重。
為了解決這個問題,TokuDB引入了專門的線程池來幫助cleaner線程快速處理熱點塊。大致處理是:如果msg buffer緩存了過多的msg,寫數(shù)據(jù)上下文就會喚醒線程池中的線程幫助cleaner快速合并當(dāng)前數(shù)據(jù)塊。
刷臟
為了加速數(shù)據(jù)處理過程,TokuDB在內(nèi)存緩存數(shù)據(jù)塊,所有數(shù)據(jù)塊組織成一個hash表,可以通過hash計算快速定位,這個hash表被稱作cachetable。InnoDB也有類似緩存機(jī)制,叫做buffer pool(簡記bp)。
內(nèi)存中數(shù)據(jù)塊被修改后不會立即寫回磁盤,而是被標(biāo)記成dirty狀態(tài)。Cachetable滿會觸發(fā)evict操作,選擇一個victim數(shù)據(jù)塊釋放內(nèi)存。如果victim是dirty的,需要先把數(shù)據(jù)寫回。Evict操作是由后臺線程evictor處理的,缺省1秒鐘運行一次,也可能由于緩存滿由server上下文觸發(fā)。
TokuDB采用激進(jìn)的緩存策略,盡量把數(shù)據(jù)保留在內(nèi)存中。除了evictor線程以外,還有一個定期刷臟的checkpoint線程,缺省60每秒運行一次把內(nèi)存中所有臟數(shù)據(jù)回刷到磁盤上。Checkpoint結(jié)束后,清理redo log文件。
TokuDB采用sharp checkpoint策略,checkpoint開始時刻把cachetable中所有數(shù)據(jù)塊遍歷一遍,對每個數(shù)據(jù)塊打上checkpointpending標(biāo)記,這個過程是拿著client端exclusive鎖的,所有INSERT/DELETE操作會被阻塞。標(biāo)記checkpointpending過程結(jié)束后,釋放exclusive鎖,server的更新請求可以繼續(xù)執(zhí)行。
隨后checkpoint線程會對每個標(biāo)記checkpoint_pending的臟頁進(jìn)行回寫。為了減少I/O期間數(shù)據(jù)塊讀寫鎖沖突,先把數(shù)據(jù)clone一份,然后對cloned數(shù)據(jù)進(jìn)行回寫;clone過程是持有讀寫鎖的write鎖,clone結(jié)束后釋放讀寫鎖,數(shù)據(jù)塊可以繼續(xù)提供讀寫服務(wù)。Cloned數(shù)據(jù)塊寫回時,持有讀寫I/O的mutex鎖,保證on-going的I/O至多只有一個。
更新數(shù)據(jù)塊發(fā)現(xiàn)是checkpoint_pending并且dirty,那么需要先把老數(shù)據(jù)寫盤。由于checkpoint是單線程,可能來不及處理這個數(shù)據(jù)塊。為此,TokuDB提供一個專門的線程池,server上下文只要把數(shù)據(jù)clone一份,然后把回寫cloned數(shù)據(jù)的任務(wù)扔給線程池處理。
Cachetable
所有緩存在內(nèi)存的數(shù)據(jù)塊按照首次訪問(cachemiss)時間順序組織成clocklist。TokuDB沒有維護(hù)LRU list,而是使用clocklist和count(可理解成age)來模擬數(shù)據(jù)塊使用頻率。
Evictor,checkpoint和cleaner線程(參見異步合并小結(jié))都是掃描clock_list,每個線程維護(hù)自己的head記錄著下次掃描開始位置。
如上圖所示,hash中黑色連線表示bucket鏈表,藍(lán)色連線表示clocklist。Evictor,checkpoint和cleaner的header分別是mclockhead,mcheckpointhead和mcleaner_head。
數(shù)據(jù)塊被訪問,count遞增(最大值15);每次evictor線程掃到數(shù)據(jù)塊count遞減,減到0整個數(shù)據(jù)塊會被evict出去。
TokuDB塊size比較大,缺省是4M;所以按照塊這個維度去做evict不是特別合理,有些partition數(shù)據(jù)比較熱需要在內(nèi)存多呆一會,冷的partition可以盡早釋放。
為此,TokuDB還提供partial evict功能,數(shù)據(jù)塊被掃描時,如果count>0并且是clean的,就把冷partition釋放掉。Partial evict對中間數(shù)據(jù)塊(包含key分布信息)做了特殊處理,把partition轉(zhuǎn)成壓縮格式減少內(nèi)存使用,后續(xù)訪問需要先解壓縮再使用。Partial evict對leaf數(shù)據(jù)塊的處理是:把partition釋放,后續(xù)訪問需要調(diào)用pf_callback從磁盤讀數(shù)據(jù),讀上來的數(shù)據(jù)也是先解壓縮的。
寫優(yōu)先
這里說的寫優(yōu)先是指并發(fā)讀寫數(shù)據(jù)塊時,寫操作優(yōu)先級高,跟行級鎖無關(guān)。
假設(shè)用戶要讀區(qū)間[210, 256],需要從root->leaf每層做binary search,在search之前要把數(shù)據(jù)塊讀到內(nèi)存并且加readlock。
如上圖所示,root(height 3)和root子數(shù)據(jù)塊(height 2)嘗試讀鎖(try_readlock)成功,但是在root的第二級子數(shù)據(jù)塊(height 1)嘗試讀鎖失敗,這個query會把root和root子數(shù)據(jù)塊(height 2)讀鎖釋放掉,退回到root重新嘗試讀鎖。
日志
TokuDB采用WAL(Write Ahead Log),每個INSERT/DELETE/CREATE INDEX/DROP INDEX操作之前會記redo log和undo log,用于崩潰恢復(fù)和事務(wù)回滾。
TokuDB的redo log是邏輯log,每個log entry記錄一個更新事件,主要包含:
- 長度1
- log command(標(biāo)識操作類型)
- lsn
- timestamp
- 事務(wù)id
- crc
- db
- key
- val
- 長度2
其中,db,key和val不是必須的,比如checkpoint就沒有這些信息。
長度1和長度2一定是相等的,記兩個長度是為了方便前向(backward)和后向(forward)掃描。
Recory過程首先前向掃描,尋找最后一個有效的checkpoint;從那個checkpoint開始后向掃描回放redo log,直至最后一個commit事務(wù)。然后把所有活躍事務(wù)abort掉,最后做一個checkpoint把數(shù)據(jù)修改同步到磁盤上。
TokuDB的undo日志是記錄在一個單獨的文件上,undo日志也是邏輯的,記錄的是更新的逆操作。獨立的undo日志,避免老數(shù)據(jù)造成數(shù)據(jù)空間膨脹問題。
事務(wù)和MVCC
相對RocksDB,TokuDB最顯著的優(yōu)勢就是支持完整事務(wù),支持MVCC。
TokuDB還支持事務(wù)嵌套,可以用來實現(xiàn)savepoint功能,把一個大事務(wù)分割成一組小事務(wù),小事務(wù)失敗只要重試它自己就好了,不用回滾整個事務(wù)。
ISOLATION LEVEL
TokuDB支持隔離級別:READ UNCOMMITTED, READ COMMITTED (default), REPEATABLE READ, SERIALIZABLE。SERIALIZABLE是通過行級鎖實現(xiàn)的;READ COMMITTED (default),和REPEATABLE READ是通過snapshot實現(xiàn)。
TokuDB支持多版本,多版本數(shù)據(jù)是記錄在頁數(shù)據(jù)塊上的。每個leaf數(shù)據(jù)塊上的 二元組,key是索引的key值(其實是拼了pk的),value是MVCC數(shù)據(jù)。這與oracle和InnoDB不同,oracle的多版本是通過undo segment計算構(gòu)造出來的。InnoDB MVCC實現(xiàn)原理與oracle近似。
事務(wù)的可見性
每個寫事務(wù)開始時都會獲得一個事務(wù)id(TokuDB記做txnid,InnoDB記做trxid)。其實,事務(wù)id是一個全局遞增的整數(shù)。所有的寫事務(wù)都會被加入到事務(wù)mgr的活躍事務(wù)列表里面。
所謂活躍事務(wù)就是處于執(zhí)行中的事務(wù),對于RC以上隔離界別,活躍事務(wù)都是不可見的。前面提到過,SERIALIZABLE是通過行級鎖實現(xiàn)的,不必考慮可見性。
一般來說,RC可見性是語句級別的,RR可見性是事務(wù)級別的。這在TokuDB中是如何實現(xiàn)的呢?
每個語句執(zhí)行開始都會創(chuàng)建一個子事務(wù)。如果是RC、RR隔離級別,還會創(chuàng)建snapshot。Snapshot也有活躍事務(wù)列表,RC隔離級別是復(fù)制事務(wù)mgr在語句事務(wù)開始時刻的活躍事務(wù)列表,RR隔離級別是復(fù)制事務(wù)mgr在server層事務(wù)開始時刻的活躍事務(wù)列表。
Snapshot可見性就是事務(wù)id比snapshot的事務(wù)id更小,意味著更早開始執(zhí)行;但是不在snapshot活躍事務(wù)列表的事務(wù)。
GC
隨著事務(wù)提交snapshot結(jié)束,老版本數(shù)據(jù)不在被訪問需要清理,這就引入了GC的問題。
為了判斷寫事務(wù)的更新是否被其他事務(wù)訪問,TokuDB的事務(wù)mgr維護(hù)了referencexids數(shù)組,記錄事務(wù)提交時刻,系統(tǒng)中處于活躍狀態(tài)snapshot個數(shù),作用相當(dāng)于referencecount。
以上描述了TokuDB如何跟蹤寫事務(wù)的引用者。那么GC是何時執(zhí)行的呢?
可以調(diào)用OPTIMIZE TABLE顯式觸發(fā),也可以在后續(xù)訪問索引key時隱式觸發(fā)。
典型業(yè)務(wù)場景
以上介紹了TokuDB引擎內(nèi)核原理,下面我們從HybridDB for MySQL產(chǎn)品的角度談一下業(yè)務(wù)場景和性能。
HybridDB for MySQL設(shè)計目標(biāo)是提供低成本大容量分布式數(shù)據(jù)庫服務(wù),一體式處理OLTP和OLAP混合業(yè)務(wù)場景,提供存儲和計算能力;而存儲和計算節(jié)點在物理上是分離的,用戶可以根據(jù)業(yè)務(wù)特點定制存儲計算節(jié)點的配比,也可以單獨購買存儲和計算節(jié)點。
HybridDB for MySQL數(shù)據(jù)只存儲一份,減少數(shù)據(jù)交換成本,同時也降低了存儲成本;所有功能集成在一個實例之中,提供統(tǒng)一的用戶接口,一致的數(shù)據(jù)視圖和全局統(tǒng)一的SQL兼容性。
HybridDB for MySQL支持?jǐn)?shù)據(jù)庫分區(qū),整體容量和性能隨分區(qū)數(shù)目增長而線性增長;用戶可先購買一個基本配置,隨業(yè)務(wù)發(fā)展后續(xù)可以購買更多的節(jié)點進(jìn)行擴(kuò)容。HybridDB for MySQL提供在線的擴(kuò)容和縮容能力,水平擴(kuò)展/收縮存儲和計算節(jié)點拓?fù)浣Y(jié)構(gòu);在擴(kuò)展過程中,不影響業(yè)務(wù)對外提供服務(wù),優(yōu)化數(shù)據(jù)分布算法,減少重新分布數(shù)據(jù)量;采用流式遷移,備份數(shù)據(jù)不落地。
除此之外,HybridDB for MySQL還支持高可用,復(fù)用鏈路高可用技術(shù),采用一主多備方式實現(xiàn)三副本。HybridDB for MySQL復(fù)用ApsaraDB for MySQL已有技術(shù)框架,部署、升級、鏈路管理、資源管理、備份、安全、監(jiān)控和日志復(fù)用已有功能模塊,技術(shù)風(fēng)險低,驗證周期短,可以說是站在巨人肩膀上的創(chuàng)新。
低成本大容量存儲場景
HybridDB for MySQL使用軟硬件整體方案解決大容量低成本問題。
軟件方面,HybridDB for MySQL是分布式數(shù)據(jù)庫,擺脫單機(jī)硬件資源限制,提供橫向擴(kuò)展能力,容量和性能隨節(jié)點數(shù)目增加而線性增加。存儲節(jié)點MySQL實例選擇使用TokuDB引擎,支持塊級壓縮,壓縮算法以表單位進(jìn)行配置。用戶可根據(jù)業(yè)務(wù)自身特點選擇使用壓縮效果好的壓縮算法比如lzma,也可以選擇quicklz這種壓縮速度快資源消耗低的壓縮算法,也可以選擇像zstd這種壓縮效果和壓縮速度比較均衡的壓縮算法。如果選用zstd壓縮算法,線上實測的壓縮比是3~4。
硬件方面,HybridDB for MySQL采用分層存儲解決方案,大量冷數(shù)據(jù)存儲在SATA盤上,少量溫數(shù)據(jù)存儲在ssd上,熱數(shù)據(jù)存儲在數(shù)據(jù)庫引擎的內(nèi)存緩存中(TokuDB cachetable)。SATA盤和ssd上數(shù)據(jù)之間的映射關(guān)系通過bcache驅(qū)動模塊來管理,bcache可以配置成WriteBack模式(寫路徑數(shù)據(jù)寫ssd后即返回,ssd中更新數(shù)據(jù)由bcache負(fù)責(zé)同步到SATA盤上),可加速數(shù)據(jù)庫checkpoint寫盤速度;也可以配置成WriteThrough模式(寫路徑數(shù)據(jù)同時寫到ssd和SATA上,兩者都ack寫才算完成)。
持續(xù)高并發(fā)寫入場景
TokuDB采用fractal tree(中文譯作分型樹)數(shù)據(jù)結(jié)構(gòu),優(yōu)化寫路徑,大部分二級索引的寫操作是異步的,寫被緩存到中間數(shù)據(jù)塊即返回。寫操作同步到葉數(shù)據(jù)塊可以通過后臺cleaner線程異步完成,也可能由后續(xù)的讀操作同步完成(讀合并)。Fractal tree在前面的內(nèi)核原理部分有詳盡描述,這里就不贅述了。
細(xì)心的朋友可能會發(fā)現(xiàn),我們在異步寫前加了個前綴:大部分二級索引。那么大部分是指那些情況呢?這里大部分是指不需要做quickness檢查的索引,寫請求直接扔給fractal tree的msg buffer即可返回。如果二級索引包含unique索引,必須先做唯一性檢查保證不存在重復(fù)鍵值。否則,異步合并(或者讀合并)無法通知唯一性檢查失敗,也無法回滾其他索引的更新。Pk字段也有類似的唯一性語義,寫之前會去查詢pk鍵值是否已存在,順便做了root到leaf數(shù)據(jù)塊的預(yù)讀和讀合并。所以,每條新增數(shù)據(jù)執(zhí)行INSERT INTO的過程不完全是異步寫。
ApsaraDB for MySQL對于日志場景做了優(yōu)化,利用INSERT IGNORE語句保證pk鍵值唯一性,并且通過把二級索引鍵值1-1映射到pk鍵值空間的方法保證二級索引唯一性,將寫操作轉(zhuǎn)換成全異步寫,大大降低了寫延遲。由于省掉唯一性檢查的讀過程,引擎在內(nèi)存中緩存的數(shù)據(jù)量大大減少,緩存寫請求的數(shù)據(jù)塊受讀干擾被釋放的可能性大大降低,進(jìn)而寫路徑上發(fā)生cachetable miss的可能性降低,寫性能更加穩(wěn)定。
分布式業(yè)務(wù)場景
HybridDB for MySQL同時提供單分區(qū)事務(wù)和分布式事務(wù)支持,支持跨表、跨引擎、跨數(shù)據(jù)庫、跨MySQL實例,跨存儲節(jié)點的事務(wù)。HybridDB for MySQL使用兩階段提交協(xié)議支持分布式事務(wù),提交階段proxy作為協(xié)調(diào)者將分布式事務(wù)狀態(tài)記錄到事務(wù)元數(shù)據(jù)庫;分區(qū)事務(wù)恢復(fù)時,proxy從事務(wù)元數(shù)據(jù)庫取得分布式事務(wù)狀態(tài),并作為協(xié)調(diào)者重新發(fā)起失敗分區(qū)的事務(wù)。
HybridDB for MySQL還可以通過判斷WHERE條件是否包含分區(qū)鍵的等值條件,決定是單分區(qū)事務(wù)還是分布式事務(wù)。如果是單分區(qū)事務(wù),直接發(fā)送給分區(qū)MySQL實例處理。
在線擴(kuò)容/縮容場景
HybridDB for MySQL通過將存儲分區(qū)無縫遷移到更多(或更少的)MySQL分區(qū)實例上實現(xiàn)彈性數(shù)據(jù)擴(kuò)展(收縮)的功能,分區(qū)遷移完成之后proxy層更新路由信息,把請求切到新分區(qū)上,老分區(qū)上的數(shù)據(jù)會自動清理。Proxy切換路由信息時會保持連接,不影響用戶業(yè)務(wù)。
數(shù)據(jù)遷移是通過全量備份+增量備份方式實現(xiàn),全量備份不落地直接流式上傳到oss。增量備份通過binlog方式同步,HybridDB for MySQL不必自行實現(xiàn)binlog解析模塊,而是利用ApsaraDB for MySQL優(yōu)化過的復(fù)制邏輯完成增量同步,通過并行復(fù)制提升性能,并且保證數(shù)據(jù)一致性。
聚合索引提升讀性能
TokuDB支持一個表上創(chuàng)建多個聚合索引,以空間代價換取查詢性能,減少回pk取數(shù)據(jù)。阿里云ApsaraDB for MySQL在優(yōu)化器上對TokuDB聚合索引做了額外支持,在cost接近時可以優(yōu)先選擇聚合索引;存在多個cost接近的聚合索引,可以優(yōu)先選擇與WHERE條件最匹配的聚合索引。
與單機(jī)版ApsaraDB for MySQL對比
與阿里云OLTP+OLAP混合方案對比
性能報告
高并發(fā)業(yè)務(wù)
壓測配置:
- 4節(jié)點,每節(jié)點8-core,32G,12000 iops,ssd盤
高吞吐業(yè)務(wù)
壓測配置:
- 8節(jié)點,每節(jié)點16-core,48G,12000 iops,ssd盤
總結(jié)
以上是生活随笔為你收集整理的mysql delete 会锁表吗_MySQL高压缩引擎TokuDB 揭秘的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: macos关闭软件更新小红点_MacOS
- 下一篇: 判定两个tensor维度相同_Tenso