在進(jìn)入正題之前,先來思考下跨節(jié)點(diǎn)的數(shù)據(jù)如何實現(xiàn)同進(jìn)退(ACID),如果對分布式事務(wù)本身有一定了解可跳過這里。如圖:
在這里插入圖片描述
假設(shè)單機(jī)數(shù)據(jù)庫的容量受限,需要將其中的數(shù)據(jù)(abcde,表示五條記錄,不一定在同一張表)分散到不同分片1(ac)、2(bd)、3(e) ,各個分片可能分布到不同的節(jié)點(diǎn),以達(dá)到擴(kuò)展容量的目的。僅僅擴(kuò)展容量是不夠的,如果每個分片只有單個節(jié)點(diǎn)有數(shù)據(jù),單點(diǎn)故障時,將出現(xiàn)數(shù)據(jù)丟失的風(fēng)險,因此需要每個分片都有多個副本如(分片1-0,分片1-1,分片1-2)分散到不同的節(jié)點(diǎn),并且通過一致性算法(raft,paxos)保證多個副本的數(shù)據(jù)一致。
現(xiàn)在來考慮同時對跨分片的數(shù)據(jù)讀寫事務(wù)操作:
T1: update a,b;
T2: update b,e;
T3: update b,a;
在單機(jī)數(shù)據(jù)庫里,對于一條記錄,會有集中的地方管理鎖信息“如MySQL: innodb_row_lock”,事務(wù)提交時很容易檢測到其是否已經(jīng)被加鎖了。但當(dāng)某個事物的多條記錄分布在不同節(jié)點(diǎn),事情就變得不一樣,假設(shè)每個節(jié)點(diǎn)都各自存儲了自身所含所有記錄的鎖信息,那么當(dāng)事務(wù)T1,更新記錄a和b時,就要分別對這兩條記錄上鎖,由于鎖信息跨節(jié)點(diǎn),因此就必須考慮如何確認(rèn)跨節(jié)點(diǎn)記錄是否被上鎖和如何搶占鎖的問題。
在單機(jī)數(shù)據(jù)庫中,分樂觀鎖和悲觀鎖,這里可以參考一下:(假設(shè)a記錄已上鎖)
樂觀鎖:在向跨節(jié)點(diǎn)的b記錄上鎖時,不管b是否已經(jīng)被上鎖,直接請求加鎖,如果鎖已存在則回滾事務(wù),然后重新從a開始事務(wù)。
悲觀鎖:在向跨節(jié)點(diǎn)的b記錄上鎖時,先詢問b是否已經(jīng)被上鎖,當(dāng)b所在節(jié)點(diǎn)反饋不存在b記錄的鎖時,則請求對b加鎖,否則進(jìn)入等待。
那么問題來了,樂觀鎖方案中,如果b更新頻繁,那么該事務(wù)回滾重試的概率大大增加,嚴(yán)重影響效率。悲觀鎖方案中,在詢問得到反饋b沒有上鎖的過程中,很可能其他的事務(wù)在這個時間差內(nèi)給b加了鎖,如何保證詢問結(jié)果反饋過程中,其他事務(wù)不能搶占(考慮是不是可以將跨節(jié)點(diǎn)的鎖信息匯總到某一處,避免兩階段確認(rèn)?),另外當(dāng)出現(xiàn)T1和T3兩個事務(wù)并發(fā)執(zhí)行,悲觀事務(wù)下出現(xiàn)了死鎖問題該怎么解決(T1占有a的鎖,等待b的鎖,T3占有b的鎖,等待a的鎖)。
既然有鎖,那就不可避免得討論MVCC的問題,如何在有鎖的情況下,實現(xiàn)跨節(jié)點(diǎn)數(shù)據(jù)的高效訪問,假設(shè)像MySQL一樣使用readview,得如何參考(統(tǒng)一的地方保存readview?每個節(jié)點(diǎn)各自保存readview?),還是有其他更好的方式。同時,事務(wù)隔離級別能得到何種程度的實現(xiàn),能否四種隔離級別都能實現(xiàn)。最后,當(dāng)鎖等待超時,如何清理鎖,MVCC版本控制,過期版本的數(shù)據(jù)如何清理?
以上的疑問,寫到這里可能還沒有答案,帶著這些疑問,來看看tidb如何應(yīng)用percolator實現(xiàn)分布式事務(wù)。
接下來進(jìn)入正題,以下內(nèi)容均是在TiDB的基礎(chǔ)上參考的。
一、percolator概覽
首先大概說下背景,谷歌搜索基于其倒序索引系統(tǒng),谷歌采用map-reduce設(shè)計了一個批量創(chuàng)建索引的系統(tǒng),解決海量數(shù)據(jù)索引創(chuàng)建的問題,但該系統(tǒng)并不擅長處理分布式海量索引數(shù)據(jù)實時更新。該系統(tǒng)存在問題我們目前并不是很了解,但是該問題有了一個指向:能否有一個支撐大量分布式數(shù)據(jù),支持并發(fā)訪問,支持跨節(jié)點(diǎn)數(shù)據(jù)實時事務(wù)更新的分布式數(shù)據(jù)庫。這也是我們上面問題的解決方向。
1、percolator數(shù)據(jù)結(jié)構(gòu)
Percolator 在 Bigtable 上抽象了 5 Columns 去存儲數(shù)據(jù),其中有 3 和事務(wù)相關(guān):
c:lock (后文中簡稱L列):
事務(wù)產(chǎn)生的鎖,映射關(guān)系為${key,start_ts}=>${lock_type,primary_key,..etc}
- key:數(shù)據(jù)的key
- start_ts:事務(wù)開始時間(分布式系統(tǒng)需要能提供全局唯一的時間戳,用來保證事務(wù)不沖突)
- lock_type:事務(wù)執(zhí)行時,會從所有執(zhí)行寫操作的行中隨機(jī)選一行作為primary,lock_type賦值為:primary_lock,其與的行l(wèi)ock_type賦值為secondary_lock。
- primary_key:當(dāng)lock_type為secondary_lock時,該值指向primary行。
c:write(后文中簡稱W列)
已提交的數(shù)據(jù)信息,存儲數(shù)據(jù)所對應(yīng)的時間戳,映射關(guān)系為
${key,commit_ts}=>${start_ts}
- key :數(shù)據(jù)的key;
- commit_ts: 事務(wù)的提交時間(同樣需要提供全局唯一的時間戳)
- start_ts:該事務(wù)開始時間,通過這個時間+key,可以從c:data列找到對應(yīng)的數(shù)據(jù)
c:data(后文中簡稱D列)
具體存儲的數(shù)據(jù),映射關(guān)系為:
{value}
- key: 真實的key
- start_ts : 對應(yīng)事務(wù)的開始時間
- value: 真實的數(shù)據(jù)值
大致看下數(shù)據(jù)組織形式:
2、Percolator 事務(wù)流程
2.1、寫操作流程
寫流程分兩個階段(兩階段提交):prewrite與commit兩個階段
2.1.1、prewrite階段
- 事務(wù)T開始,從全局時間授權(quán)模塊(Timestamp Oracle(TSO))獲取start_ts,在所有需要寫操作的行中選一個作為primary,其他的均為secondary。
- 對primary行寫入L列。即上鎖,上鎖前會檢查是否有沖突:
- 該行已有其他客戶端(session)上鎖(primary_lock或secondary_lock)
- 該行最新W列的commit_ts>當(dāng)前事務(wù)T的start_ts(說明當(dāng)前事務(wù)開始后,有其他的事務(wù)先提交了對改行的修改)
當(dāng)出現(xiàn)以上兩種情況時,說明發(fā)生沖突,上鎖失敗,回退當(dāng)前事務(wù)。否則上鎖成功,當(dāng)前事務(wù)寫入L列(key=>T.key,start_ts=>T.start_ts,lock_type=>primary_lock,primary_key=>null。因為改行本身就是primary行,因此primary行指向null)
- 上鎖成功后,寫入D列更新即新版本的數(shù)據(jù),以start_ts為版本號。由于當(dāng)前數(shù)據(jù)未提交,D列對外不可見。(不可見的意思就是沒有寫W列,客戶端查詢通過W列查找版本號)
- primary行上鎖流程結(jié)束后,開始secondary行的prewrite流程,流程與primary行上鎖流程相似,但是鎖類型不一樣,且鎖的內(nèi)容會多出對primary行的指向(lock_type=>secondary_lock,primary_key=>primary_key)
- prewrite流程任何一步發(fā)生錯誤,都會回滾,刪除新加的L列和新加的D列。
由上流程可以看出,數(shù)據(jù)的寫入(寫D列)是在prewrite流程完成,為什么在prewrite階段就寫了數(shù)據(jù)呢?為何不在commit階段寫?別急,繼續(xù)往下看。
2.1.2、commit階段
- commit階段開始時,從全局時間授權(quán)模塊獲取commit_ts,commit_ts>start_ts.
- commit primary行,寫入對應(yīng)的W列數(shù)據(jù)(key,commit_ts,start_ts)表明版本號start_ts對應(yīng)的數(shù)據(jù)已提交。
- 刪除primary行的L列
- 如果primary行提交失敗,整個事務(wù)回滾,刪除新加的L列和新加的D列。如果primary行提交成功,則可異步提交其余的secondary行,secondary行的提交失敗了也無所謂。
這里就可以看出,在primary行commit成功后(寫入W列,刪除L列),secondary行的commit就可以異步提交了,即便異步commit secondary行階段發(fā)生異常(或者下次訪問時還沒來得及)沒有清理鎖,沒有W列也沒有關(guān)系,可以在下次讀寫的時候,根據(jù)secondary行的L列信息找到primary行(通過primary_key+start_ts)判斷是否已經(jīng)提交該事務(wù)(因此需要先寫W列再刪L列,如果primary行存在L列,說明事務(wù)還未提交,如果不存在L列,但存在對應(yīng)W列,說明事務(wù)已提交,如果primary行同時不存在對應(yīng)L列和W列,說明該事務(wù)回滾了)。
如果在commit階段寫入D列數(shù)據(jù),假設(shè)是一行一行的提交并刪除L列,那么肯定會出現(xiàn)部分可見部分不可見的情況,比如primary行commit了,寫了D列并可見,其余的行還沒有寫入D列,就沒法通過檢測primary行commit的情況,來判斷其余行的數(shù)據(jù)的可見性了額,因為沒有D列的數(shù)據(jù)。那么這種方式就得等所有行的D列都寫入后才能開始刪鎖向客戶端響應(yīng)事務(wù)狀態(tài),這樣的話,就大大增加了這一部分?jǐn)?shù)據(jù)加鎖的時間了,同時也可能因為網(wǎng)絡(luò)原因只有部分行完成了commit響應(yīng)導(dǎo)致事務(wù)超時。
這里埋一個疑問,如果primary行的數(shù)據(jù)提交后被刪除了,沒有及時清理鎖的secondary行該如何確定自身是否需要提交呢?
以上流程每行出現(xiàn)的L列、W列、D列,都會通過一致性算法(raft)復(fù)制多個副本到多個節(jié)點(diǎn)。
2.2、讀操作流程
兩階段提交,prewrite寫D列,但不可見,commit階段寫W列后可見,W列表示一個可見性,另一個類似于索引。當(dāng)下一個事務(wù)T2讀寫查詢時,將進(jìn)行如下流程:
- 檢查該行在T2.start_ts 這個時間點(diǎn)之前是否有L列,如果有則等待解鎖,或者等待超時嘗試清除。不可繞過L列直接訪問W列查找更老版本start_ts_old的數(shù)據(jù),否則產(chǎn)生幻讀(該L列對應(yīng)的版本提交后,T2.start_ts與start_ts_old之間就有多一個版本的數(shù)據(jù))
- 該行不存在鎖時,讀取至T2.start_ts的最新數(shù)據(jù),從start_ts開始倒序查找W列,找到對應(yīng)的列中的start_ts,通過該值從D列找到對應(yīng)的數(shù)據(jù)。
二、MVCC版本控制與鎖清除、過期版本數(shù)據(jù)清除
1、TiDB的MVCC
由上面的內(nèi)容可知,在某一行的W列中,包含一個commit_ts,一個start_ts。前者是數(shù)據(jù)真正對外可見的標(biāo)志,后者是寫入該數(shù)據(jù)的事務(wù)開始時間。兩者都是全局唯一,均可作為當(dāng)期數(shù)據(jù)的版本號,但以可用性來講,用commit_ts作為版本號更為直接。
當(dāng)系統(tǒng)運(yùn)行一段時間,某行數(shù)據(jù)經(jīng)歷Tn次事務(wù)更新,在沒有清除歷史版本數(shù)據(jù)的情況下,就有Tn個版本的D列和W列,此時如果有第Tn+1事務(wù)開始讀該行,開始時間為Tn+1.start_ts。
假設(shè)Tn.commit_ts <Tn+1.start_ts,那么Tn版本的數(shù)據(jù)對于Tn+1是可見的,同時(Tn.commit_ts Tn+1.start_ts]這個時間段內(nèi),該行不能有L列,否則Tn+1對該行的讀將會阻塞,以防產(chǎn)生幻讀。但是如果L列出現(xiàn)在(Tn+1.start_ts,+∞)時間段則不阻塞讀。通過該機(jī)制很容易實現(xiàn)對歷史版本數(shù)據(jù)的訪問。
總的來說就是,新寫入的數(shù)據(jù)不會覆蓋舊的數(shù)據(jù),而且通過版本號來區(qū)分版本,即便是delete或者truncate操作,數(shù)據(jù)也不會馬上清除,而是等待GC的任務(wù)執(zhí)行來清理。
2、鎖清除與過期版本數(shù)據(jù)清除
TiDB本身包含GC機(jī)制用來清理歷史數(shù)據(jù)(默認(rèn)配置下,GC 每 10 分鐘觸發(fā)一次,每次 GC 會保留最近 10 分鐘內(nèi)的數(shù)據(jù))。流程如下:
計算safe point時間點(diǎn)(如何計算?每行記錄safe point不一樣?)
清理鎖,首先對于primary_lock,如果鑒定超時,則直接刪除并且回滾對應(yīng)事務(wù),其次secondary_lock, 如果對應(yīng)的primary行已提交,對secondary_lock對應(yīng)的行也應(yīng)該提交,否則回滾;如果 Primary 仍然是上鎖的狀態(tài)(沒有提交也沒有回滾),則應(yīng)當(dāng)將該事務(wù)視為超時失敗而回滾。
這里回到上面的問題,如果secondary_lock找不到primary行怎么辦;是否可認(rèn)為primary行因回滾而刪除而不是因為GC而被刪除?這里需要明確的是,需要清理的都是位于上一次safepoint和此次safepoint之間的版本數(shù)據(jù)。因此需要先清理鎖,保證secondary_lock能夠找到primary行,這樣的話,只要找不到primary行就可以認(rèn)為該secondary_lock對應(yīng)的事務(wù)回滾了。進(jìn)行GC清理。刪除所有Key在safePoint之前的數(shù)據(jù)(每個 key 保留 safe point 前的最后一次寫入,除非最后一次寫入是刪除),對于drop和truncate的大量連續(xù)數(shù)據(jù)刪除,將通過連續(xù)區(qū)間刪除的方式。
(v2.1以前GC是對每一個region操作,v3.0之后,只對region leader 操作GC)
三、樂觀事務(wù)與悲觀事務(wù)
在TiDB 新特性漫談:悲觀事務(wù)一文中,作者用了一個比方:去餐廳吃飯,來說明樂觀事務(wù)和悲觀事務(wù):
樂觀事務(wù): 不管有沒有位,直接去餐廳,去到要是沒有位子就回來。
悲觀事務(wù): 先電聯(lián)餐廳確定有沒有位置,預(yù)訂位置,然后再去。
如果餐廳的位置很多(沖突率低),那么直接去餐廳有座的概率就高,但是一旦每座就要白跑一趟;加入餐廳經(jīng)常人滿為患,直接去大概率就是要白跑一趟,這種情況下,打電話提前問下沒有座位,然后支付定金預(yù)定代價更低。
這也引申出兩種事務(wù)模型的適用場景:對于并發(fā)事務(wù)沖突率高的場景(某部分?jǐn)?shù)據(jù)頻繁修改)的場景。悲觀事務(wù)控制可以避免因沖突而回滾的問題,但在沖突率比較低的場景,悲觀事務(wù)比樂觀事務(wù)性能要差。
1、樂觀事務(wù)
percolator本身就是基于樂觀事務(wù)模型的實現(xiàn),來看下我從TiDB官網(wǎng)偷來的圖:
由第一章可知道percolator通過兩階段提交保證事務(wù)原子性(prewrite和commit兩個階段),在傳統(tǒng)的2PC概念里,需要有一個專門的協(xié)調(diào)者(事務(wù)管理者)來記錄事務(wù)狀態(tài),協(xié)調(diào)者如若宕機(jī),事務(wù)的狀態(tài)將得不到保證。然而在percolator中弱化了這一概念,取而代之的是通過L列和W列來標(biāo)志事務(wù)的狀態(tài)(參考第一章中為何可以異步提交secondary行)。
1.1、 percolator的prewrite階段主要做了兩件事,寫數(shù)據(jù)(寫D列,但是沒有W列,對外不可見)和加鎖(寫L列),這也是會出現(xiàn)沖突的階段。
首先在TIDB-server中,會先對落在同一個TIDB-server執(zhí)行的不同事務(wù)進(jìn)行沖突檢測,如有沖突將不會再tikv中開始percolator的兩階段提交。但是TiDB-Server無法感知到其他TiDB-Server的檢測情況,所以分布在其他TiDB-Server執(zhí)行的事務(wù)檢測沖突就得到tikv中判斷了。當(dāng)然也可以關(guān)閉TiDB-Server的沖突檢測,全部在tikv層檢測。
寫寫沖突:寫寫沖突存在兩種情況:1、事務(wù)T1與事務(wù)T2,同時寫某行時,T2先寫了L列,T1此時會檢測到?jīng)_突執(zhí)行回滾;2、在T1獲取start_ts1后到發(fā)起prewrite這段時間內(nèi),T2迅速獲取start_ts2占有鎖,并執(zhí)行完兩階段提交,此時會存在最新W列的commit_ts>T1.start_ts1,此時也會回滾。
-
讀寫沖突:事務(wù)T獲取到start_ts,開始查詢[0,start_ts]期間的最新數(shù)據(jù),結(jié)果發(fā)現(xiàn)這個期間存在L列,此時發(fā)生讀寫沖突。
對于讀寫沖突,可以參考第一章2.2中有說到如何解決。而對于寫寫沖突,在樂觀事務(wù)模型中無法避免,可通過合并小事務(wù),減少不同事務(wù)間的發(fā)生沖突的幾率,但是事務(wù)不能過大,大事務(wù)容易導(dǎo)致OOM問題,同時數(shù)據(jù)準(zhǔn)備時間長跟其他事務(wù)發(fā)生沖突的幾率也會增大,同時事務(wù)提交的時間也增大。官方建議100-500行之間一個事務(wù),但是具體得根據(jù)業(yè)務(wù)特性,比如插入遠(yuǎn)遠(yuǎn)多于更新的業(yè)務(wù)系統(tǒng),該值可以考慮加大。
1.2、在commit階段,也做了兩件事,讓數(shù)據(jù)對外可見(寫W列),刪除L列。
這個階段容易出現(xiàn)鎖超時或者因為網(wǎng)絡(luò)問題導(dǎo)致鎖殘留的問題。但只要保證primary行完成提交,事務(wù)就可以任務(wù)提交完成,否則整個事務(wù)回退。(可參考第二章鎖清除的內(nèi)容)
樂觀事務(wù)模型中,在高沖突率的場景,事務(wù)很容易提交失敗。TiDB 樂觀事務(wù)提供了重試機(jī)制,但tidb默認(rèn)不重試事務(wù),重試時將重新獲取start_ts,必將導(dǎo)致無法保證可重復(fù)讀。為解決高沖突場景重試的問題,在tidb v3.0之后的版本中,實現(xiàn)了悲觀事務(wù),并從v3.0.8 開始,新創(chuàng)建的 TiDB 集群默認(rèn)使用悲觀事務(wù)模型。
2、悲觀事務(wù)
如果要解決percolator的prewrite階段的鎖沖突導(dǎo)致的回滾問題,那么就要開始兩階段提交前就要先檢測鎖沖突的情況。但是TIDB是個分布式系統(tǒng),即便同一個tidb-server上的不同事務(wù)可以檢測鎖沖突,但是不同的tidb-server上的不同事務(wù)之間,就要有一個共同的地方保存鎖信息共享鎖信息。事實上TIDB也是這么做的。我又偷了一張官網(wǎng)的圖:
TiDB的悲觀事務(wù)是在樂觀事務(wù)的基礎(chǔ)上改過來的,可以看出跟樂觀事務(wù)不同是上圖紅圈的部分,即開始2PC之前就進(jìn)行鎖沖突檢測。
由圖可以看出,TIDB將鎖存儲在tikv中以便所有的TIDB-server共享,并通過raft算法將鎖內(nèi)容復(fù)制到多個節(jié)點(diǎn)防止單點(diǎn)故障。
在悲觀事務(wù)下,在percolator的prewrite階段將不會出現(xiàn)寫寫沖突,但是依然存在讀寫沖突的情況。
寫請求遇到悲觀鎖時,等待解鎖或者鎖超時即可。但是并發(fā)事務(wù)請求鎖資源時,可能存在死鎖情況,比如T1: update a,b;T3: update b,a;T1經(jīng)過sql解析后,向tikv寫入a行的鎖,同時T2向tikv寫入了b行的鎖,此時T1/T2相互等待對方釋放鎖,出現(xiàn)了死鎖的情況。
在樂觀事務(wù)中,經(jīng)過tidb-server 的解析,在prewrite階段遇到鎖沖突會回滾重試(鎖超時時間很短),這個方式避免了死鎖。
在悲觀事務(wù)中需要能夠檢測并發(fā)的不同事務(wù)之間是否有循環(huán)依賴的關(guān)系存在,并在檢測到循環(huán)依賴時(從tikv存儲的鎖信息中,很容易判斷不同事務(wù)之間是否存在循環(huán)依賴的鎖關(guān)系)提供仲裁,中止其中一個事務(wù)或讓其重試(因其還沒進(jìn)入2PC階段,因此代價很低)。TIDB的死鎖檢測機(jī)制是異步進(jìn)行的,不影響正常寫鎖和后續(xù)流程進(jìn)行,因此對不存在死鎖的事務(wù)不會產(chǎn)生延遲。
悲觀事務(wù)下,與MySQLinnodb的部分差異,這部分內(nèi)容跟本文的原理內(nèi)容關(guān)系不大,僅做匯總記錄用。
- 有些 WHERE 子句中使用了 range,TiDB 在執(zhí)行這類 DML 語句和 SELECT FOR UPDATE 語句時,不會阻塞 range 內(nèi)并發(fā)的 DML 語句的執(zhí)行。
舉例:
CREATE TABLE t1 (
id INT NOT NULL PRIMARY KEY,
pad1 VARCHAR(100)
);
INSERT INTO t1 (id) VALUES (1),(5),(10);
BEGIN /*T! PESSIMISTIC /;
SELECT * FROM t1 WHERE id BETWEEN 1 AND 10 FOR UPDATE;
BEGIN /T! PESSIMISTIC */;
INSERT INTO t1 (id) VALUES (6); -- 僅 MySQL 中出現(xiàn)阻塞。
UPDATE t1 SET pad1='new value' WHERE id = 5; -- MySQL 和 TiDB 處于等待阻塞狀態(tài)。
產(chǎn)生這一行為是因為 TiDB 當(dāng)前不支持 gap locking(間隙鎖)。- TiDB 不支持 SELECT LOCK IN SHARE MODE,使用這個語句執(zhí)行的時候,效果和沒有加鎖是一樣的,不會阻塞其他事務(wù)的讀寫。
- DDL 可能會導(dǎo)致悲觀事務(wù)提交失敗。MySQL 在執(zhí)行 DDL 語句時,會被正在執(zhí)行的事務(wù)阻塞住,而在 TiDB 中 DDL 操作會成功,造成悲觀事務(wù)提交失敗。(說明TiDB 沒有實現(xiàn)類似MySQL的 MDL機(jī)制)
- START TRANSACTION WITH CONSISTENT SNAPSHOT 之后,MySQL 仍然可以讀取到之后在其他事務(wù)創(chuàng)建的表,而 TiDB 不能。
四、事務(wù)隔離級別
1、可重復(fù)讀
TiDB 實現(xiàn)了快照隔離 (Snapshot Isolation, SI) 級別的一致性。為與 MySQL 保持一致,又稱其為“可重復(fù)讀”。TiDB的事務(wù)隔離級別也是通過MVCC檢測版本可見性。
- 只能讀到該事務(wù)啟動時已經(jīng)提交的其他事務(wù)修改的數(shù)據(jù)
- 未提交的數(shù)據(jù)或在事務(wù)啟動后其他事務(wù)提交的數(shù)據(jù)是不可見的
- 處于可重復(fù)讀隔離級別的事務(wù)不能并發(fā)的更新同一行,當(dāng)事務(wù)提交時發(fā)現(xiàn)該行在該事務(wù)啟動后,已經(jīng)被另一個已提交的事務(wù)更新過,那么該事務(wù)會回滾(因當(dāng)前事務(wù)的start_ts < 最新已提交事務(wù)的commit_ts。)
此處與MySQL不同,MySQL在可重復(fù)讀隔離級別下,并發(fā)的兩個不同的事務(wù)生成的不同readview是可以更新同一條記錄的。MySQL的可重復(fù)讀隔離級別針對的是快照讀,而對于update,select for update此類當(dāng)前讀并不隔離。因此MySQL可重復(fù)讀的一致性要弱于TiDB。
2、讀已提交
從 TiDB v4.0.0-beta 版本開始,TiDB 支持使用 Read Committed 隔離級別。read Committed 隔離級別僅在悲觀事務(wù)模式下生效。在樂觀事務(wù)模式下設(shè)置事務(wù)隔離級別為 Read Committed 將不會生效,事務(wù)將仍舊使用可重復(fù)讀隔離級別。
(這里暫時還沒搞明白為何樂觀事務(wù)模式下設(shè)置事務(wù)隔離級別為 Read Committed 將不會生效)
參考
Percolator 論文筆記
經(jīng)典論文翻譯導(dǎo)讀之《Large-scale Incremental Processing Using Distributed Transactions and Notifications》
Percolator分布式事務(wù)模型原理與應(yīng)用
Percolator 和 TiDB 事務(wù)算法
TiDB 鎖沖突問題處理
TiKV 的 MVCC(Multi-Version Concurrency Control)機(jī)制
GC 機(jī)制
TiDB 樂觀事務(wù)模型
TiDB 樂觀事務(wù)原理與實踐
TiDB樂觀事務(wù)
樂觀事務(wù)模型下寫寫沖突問題排查
TiDB 4.0 新特性前瞻(二)白話“悲觀鎖”
TiDB 悲觀事務(wù)模型
TiDB 新特性漫談:悲觀事務(wù)
悲觀事務(wù)
TiDB 事務(wù)隔離級別
兩階段提交與三階段提交
關(guān)于分布式事務(wù)、兩階段提交協(xié)議、三階提交協(xié)議