mysql中mvcc的具體使用
一 MVCC的作用
1.1 mvcc的作用
1.MVCC(Multiversion Concurrency Control)多版本并發(fā)控制。即通過數(shù)據(jù)行的多個版本管理來實現(xiàn)數(shù)據(jù)庫的并發(fā)控制,使得在InnoDB事務(wù)隔離級別下執(zhí)行一致性讀
操作有了保障。
2.mysql中的InnoDB中實現(xiàn)了MVCC主要是為了提高數(shù)據(jù)庫的并發(fā)性能,在無鎖的情況下也能處理讀寫并發(fā),大大提高數(shù)據(jù)庫的并發(fā)度。
3..MySQl中只有InnoDB支持MVCC,其他存儲引擎不支持
4.為了查詢一些正在被其他事務(wù)更新的值的時候,能夠查到它們被更新之前的值,這樣做就能在查詢的時候不必等待更新事務(wù)的提交。
在InnoDB中,會對增刪改操作自動添加排它鎖,因此兩個事務(wù)不會出現(xiàn)臟寫的情況,也就是不會出現(xiàn)兩個事務(wù)交叉著對同一條記錄進行修改,必須等待第一個事務(wù)提交才能進行第二個事務(wù)。
1.2 快照讀與當前讀的區(qū)別與聯(lián)系
1.MVCC在InnoDB中的實現(xiàn)主要是為了提高數(shù)據(jù)庫的并發(fā)性能,用更好的方式處理讀寫沖突,做到即使有讀寫沖突,也能不加鎖實現(xiàn)非堵塞并發(fā)讀,這個讀指的是快照讀
而不是當前讀。
2.當前讀實質(zhì)上是一種加鎖的操作,是悲觀鎖的體現(xiàn);而MVCC是采用樂觀鎖的一種方式
1.3 快照讀
1.快照讀,顧名思義讀取的是一份快照數(shù)據(jù),所以讀到的并不一定是最新數(shù)據(jù),可能是歷史數(shù)據(jù)。
2.簡單的select查詢就是快照讀,不加鎖非阻塞讀,降低數(shù)據(jù)庫的開銷。
3.但是快照讀在隔離級別是串行化級別是沒有意義的,因為串行化的sql都是排隊執(zhí)行的,不存在并發(fā),所以就會變成當前讀。
1.4 當前讀
當前讀獲取的數(shù)據(jù)是最新數(shù)據(jù),而且在讀取時不能被其他修改的,所以會對讀取的記錄加鎖來控制。
加鎖的SELECT(共享或排它鎖)
或者對數(shù)據(jù)進行增刪改操作(自動添加排它鎖)
都會進行當前讀。
select * from ajisun where id > 1 lock in share mode;</code><code>// 或者</code><code>select * from ajisun where id >1 for update;
1.5 mvcc可以解決問題
讀寫之間的堵塞問題,提高事務(wù)的并發(fā)讀寫能力
降低了死鎖的概率,MVCC采用了樂觀鎖的方式,讀取數(shù)據(jù)的時候不需要加鎖,對于寫操作,也只要鎖定必要的行
解決快照讀問題,當查詢數(shù)據(jù)庫某個時間節(jié)點的快照的時候,只能查看到在這個節(jié)點之前提交的事務(wù)的結(jié)果而看不到時間點之后事務(wù)提交的更新結(jié)果
1.6 mvcc面試題:mvcc是怎么實現(xiàn)的
mvcc 是多版本并發(fā)控制,通過生成記錄的歷史版本解決幻讀問題,并提高數(shù)據(jù)庫的性能,無鎖實現(xiàn)讀寫并發(fā)操作。
1.mvcc 的實現(xiàn)主要是通過三個隱藏字段,undo log以及readView 實現(xiàn)的。
2.三個隱藏字段分別是隱藏主鍵,事務(wù)ID,回滾指針。
3.undo log是各個事務(wù)修改同一條記錄的時候生成的歷史記錄,方便回滾,同時會生成一條版本鏈。
4.readView是事務(wù)在進行快照讀的時候生成的記錄快照,用于判斷數(shù)據(jù)的可見性。
5.描述readView 可見性判斷規(guī)則。
二 MVCC實現(xiàn)原理
2.1 原理
? MVCC的實現(xiàn)依賴于:隱藏字段
、Undo log
、Read View
2.2 undo log
2.2.1 undo Log的作用
所謂的版本鏈就是在MVCC中,多個事務(wù)對同一行記錄進行更新會產(chǎn)生多個歷史快照,這些記錄保存在Undo Log里,這些undo日志通過回滾指針串聯(lián)在一起。
undo log就是回滾日志,在insert/update/delete變更操作的時候生成的記錄方便回滾。
當進行insert操作的時候,產(chǎn)生的undo log只有在事務(wù)回滾的時候需要,如果不回滾在事務(wù)提交之后就會被刪除。
當進行update和delete的時候,產(chǎn)生的undo log不僅僅在事務(wù)回滾的時候需要,在快照讀的時候也是需要的,所以不會立即刪除,只有等不再用到這個日志的時候才會被mysql purge線程統(tǒng)一處理掉(delete操作也只是打一個刪除標記,并不是真正的刪除)。
2.2.2 undo Log的組成
? undo log的版本鏈,對于使用InnoDB存儲引擎的表來說,它的聚簇記錄中包含兩個必要的索引列:
1.trx_id:每次事務(wù)對聚簇記錄進行修改的時候,就會將該事務(wù)的id復制給trx_id
隱藏列
2.roll_pointer:每次對每條聚簇索引進行改動的時候,都會將舊的版本信息寫入undo log
中,通過回滾指針就能找到記錄修改前的信息。
2.2.3 undo Log的案例
1.假設(shè)兩個事務(wù)id分別為10、20的事務(wù)分別對這條記錄進行Update
操作。
2.每次對記錄進行改動,都會記錄一條undo log
,每個undo log
都包含創(chuàng)建它的事務(wù)id,每條undo log都會有一個roll pointer
(INSERT操作不會有,因為插入沒有更新的版本),這些undo log
通過roll pointer
連接起來,串成一個鏈表,這個鏈表就成為undo log 版本鏈。
3.如下圖:
2.3 隱藏字段
除了我們正常業(yè)務(wù)涉及的字段外,InnoDB在內(nèi)部向數(shù)據(jù)庫表中添加三個隱藏字段:
1.DB_TRX_ID:6-byte的事務(wù)ID。插入或更新行的最后一個事務(wù)的事務(wù)ID
2.DB_ROLL_PTR:7-byte的回滾指針。就是指向?qū)承杏涗浀纳弦粋€版本,在undo log中使用。
3.DB_ROW_ID:6-byte的隱藏主鍵。如果數(shù)據(jù)表中沒有主鍵,那么InnoDB會自動生成單調(diào)遞增的隱藏主鍵(表中有主鍵或者非NULL的UNIQUE鍵時都不會包含 DB_ROW_ID列)。
如上面的表沒有設(shè)計primary key,其中id/name/city是我們的業(yè)務(wù)字段,那么加上隱藏字段應該如下
2.4 ReadView
2.4.1 ReadView的作用
ReadView 是事務(wù)快照讀的時候產(chǎn)生的數(shù)據(jù)讀視圖,在該事務(wù)執(zhí)行快照讀的那一刻,會生成一個數(shù)據(jù)系統(tǒng)當前的快照,記錄并維護系統(tǒng)當前活躍事務(wù)的id,事務(wù)的id值是遞增的。
Read View
就是事務(wù)在使用MVCC機制在進行快照讀操作時產(chǎn)生的快照,ReadView 的最大作用就是判斷數(shù)據(jù)的可見性,當某個事務(wù)執(zhí)行快照讀的時候,會對此記錄創(chuàng)建一個ReadView 的視圖,在整個事務(wù)期間根據(jù)某些條件判斷該事務(wù)能夠看到的版本鏈上的哪條歷史數(shù)據(jù)。
2.4.2 ReadView的組成
creator_id:創(chuàng)建這個Read View的事務(wù)id
trx_ids:表示創(chuàng)建這個Read View的時候正在活躍的事務(wù)id列表
up_limit_id:活躍的事務(wù)中最小的id
low_limit_id:表示生成low_limit_id時系統(tǒng)應該分配給下一個事務(wù)的
id
值,low_limit_id是系統(tǒng)最大的事務(wù)id(而不是活躍的最大事務(wù)id)
low_limit_id 并不是trx_ids的最大值,而是系統(tǒng)能夠分配的事務(wù)id最大值,事務(wù)id是遞增分配的,并且只有事務(wù)在進行增刪改操作的時候才會分配事務(wù)ID。比如現(xiàn)在有1 2 5三個事務(wù),那么id為5的事務(wù)提交后,一個新事務(wù)在生成ReadView的時候,trx_ids就包括1 2,up_limit_id就是1,low_limit_id就是6
此時如果有事務(wù)創(chuàng)建Read View,則
- trx_ids=[trx2, trx3, trx5, trx8]
- up_limit_id=trx2
- low_limit_id=trx8+1
2.4.3 ReadView的判斷流程
當查詢一條數(shù)據(jù)的時候,系統(tǒng)
- 首先獲取查詢操作的事務(wù)的版本號
- 獲取當前系統(tǒng)的
ReadView
- 將查詢到的數(shù)據(jù)與
ReadView
中的事務(wù)版本號進行比較 - 如果不符合
ReadView
的規(guī)則,則通過回滾指針
形成的Undo Log版本鏈
從undo log
中獲取符合規(guī)則的歷史快照 - 返回符合規(guī)則的數(shù)據(jù)
快照記錄創(chuàng)建這個Read View的事務(wù)id、活躍的事務(wù)中最小的id、系統(tǒng)最大的事務(wù)id,并且InnoDB會為每個事務(wù)構(gòu)建了一個數(shù)組,用來記錄并維護系統(tǒng)當前活躍事務(wù)
的ID(活躍指的是啟動了還沒有提交),等到訪問某條記錄的時候,就可以根據(jù)上面記錄的內(nèi)容判斷記錄版本對當前事務(wù)可不可見:
1.如果當前被訪問記錄的trx_id屬性值與ReadView中的creator_trx_id值相同,說明當前事務(wù)修改的記錄就是在當前事務(wù)下操作的,那當然是對我們可見的了,因此可以修改這條記錄
2.如果當前被訪問記錄的trx_id屬性值小于ReadView中up_limit_id值,說明(生成該版本的事務(wù)在該事務(wù)生成readView之間已經(jīng)提交) 即當前事務(wù)在開啟的時候,這條記錄最近依次被其他事務(wù)操作的事務(wù)已經(jīng)提交了,所以對這條記錄對我們來說也是可以見,可以修改
3.如果當前被訪問記錄的trx_id屬性值大于或者等于ReadView中l(wèi)ow_limit_id值,說明(生成該版本的事務(wù)在當前事務(wù)生成readView之后才開啟) 即:我們開啟事務(wù)未修改該記錄之前,已經(jīng)有另外一個事務(wù)開啟,并且正在修改該事務(wù)了,因此,這條記錄對我們來說依然是不可見的,我們不能修改。
4.如果當前被訪問記錄的trx_id屬性值介于ReadView中 up_limit_id 和 low_limit_id 之間的話,那么此時就需要分情況討論了,此時我們應該分析該trx_id是否在 trx_ids 中
如果存在,說明(創(chuàng)建ReadView時,生成該版本的事務(wù)還處于活躍狀態(tài)) 即:當前已經(jīng)有其它的事務(wù)正在修改該條記錄,并且還未提交,此時這條記錄對我們不可見
如果不存在,說明((創(chuàng)建ReadView時,生成該版本的事務(wù)已經(jīng)提交) 即:此時沒有事務(wù)操作該條記錄,我們可以修改該條記錄
5.如果某個版本對當前事務(wù)不可見,那么順著版本鏈找到下個版本記錄,然后繼續(xù)上面的對比規(guī)則,直到找到版本鏈中的最后一個版本,如果最后一個版本都不可見,那么該條記錄對此事務(wù)完全不可見,也就查不到這個記錄。
2.5 不同隔離級別使用Readview
1.讀未提交:能夠讀取未提交的事務(wù)修改的數(shù)據(jù),所以直接讀取最新的記錄就可以,不必使用MVCC
2.讀已提交:不能讀取未提交的事務(wù)修改的數(shù)據(jù),并且不能進行重復讀取,事務(wù)中,每次快照讀都會新生成一個快照和ReadView,這就是我們在RC級別下的事務(wù)中可以看到別的事務(wù)提交的更新的原因。
3.可重復讀:不能讀取未提交的事務(wù)修改的數(shù)據(jù),并且能進行重復讀取,所以只在第一次查詢的時候獲取一次ReadView
,之后查詢都只查看已經(jīng)生成的ReadView副本
4.可串行化:InnoDB規(guī)定使用加鎖的方式來訪問記錄,通過加鎖的方式讓所有sql都串行化執(zhí)行了,也是讀最新的,不存在快照讀ReadView。
2.6 mvcc解決幻讀問題
MySQL在Repeatable Read
隔離級別下是可以解決幻讀問題的,解決的方案有兩種:
1.使用MVCC進行快照讀,寫使用臨鍵鎖。
添加的臨鍵鎖不會影響快照讀,只會影響到想要獲取鎖的讀操作
2.讀寫加鎖,也就是使用可串行化的隔離模式。
2.6.1 mvcc解決幻讀
讀操作利用多版本并發(fā)控制
(MVCC
),寫操作加鎖。
MVCC
就是生成一個ReadView
,通過ReadView
能夠找到符合條件的記錄版本(歷史版本由undo log
提供查詢),查詢語句執(zhí)行查詢已經(jīng)提交的事務(wù)做出的更改,對于沒由提交的事務(wù)和ReadView
創(chuàng)建之后的事務(wù)做出的更改是看不到的。而寫操作肯定是針對的最新版本的記錄
,因此讀記錄的歷史版本和寫操作的最新記錄版本并不會沖突,也就是采用MVCC時,讀寫操作并不會沖突。
普通的SELECT語句在READ COMMITTED 和 REPEATABLE READ隔離級別下的讀操作就是利用MVCC進行的讀
1.READ COMMITTED:由于不會讀取沒有提交的事務(wù)修改的數(shù)據(jù)版本,因此避免了臟讀問題
2.REPEATABLE READ:由于不會讀取Read View
創(chuàng)建之后的事務(wù)更改的數(shù)據(jù)(一個事務(wù)只有在第一次執(zhí)行SELECT語句才會生成一個Read View
,之后的SELECT語句都在復用),因此避免了可重復讀和幻讀問題。
2.6.2 通過加鎖的方式
讀、寫操作都采用加鎖的方式
在一些業(yè)務(wù)場景中,不允許讀取數(shù)據(jù)的歷史版本,即每次都需要去讀取磁盤中最新的數(shù)據(jù),這樣也就意味著讀操作
也需要和寫操作
一樣排隊執(zhí)行。
如此一來,臟讀
和不可重復讀
問題都得到了解決,因為讀操作和寫操作的串行執(zhí)行,不會出現(xiàn)一個事務(wù)讀取另一個未提交事務(wù)的數(shù)據(jù)以及一個事務(wù)讀取過程中另一個事務(wù)修改數(shù)據(jù)提交導致前一個事務(wù)前后讀取數(shù)據(jù)不一致的情況(第二個事務(wù)根本無法開始)。
****但是,幻讀
問題有些尷尬,試想一個事務(wù)在進行讀操作,因此給表中的一定范圍內(nèi)的數(shù)據(jù)加鎖,但是另一個事務(wù)要寫的這個幻影數(shù)據(jù)
可不在這個范圍里面,也就是兩個讀寫操作并不會沖突,仍然會出現(xiàn)幻讀問題
,解決這一個問題的辦法就是寫操作使用臨鍵鎖
到此這篇關(guān)于mysql中mvcc的具體使用的文章就介紹到這了,更多相關(guān)mysql mvcc內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL實現(xiàn)樹狀所有子節(jié)點查詢的方法
這篇文章主要介紹了MySQL實現(xiàn)樹狀所有子節(jié)點查詢的方法,涉及mysql節(jié)點查詢、存儲過程調(diào)用等操作技巧,具有一定參考借鑒價值,需要的朋友可以參考下2016-06-06MySQL臟讀幻讀不可重復讀及事務(wù)的隔離級別和MVCC、LBCC實現(xiàn)
這篇文章主要介紹了MySQL臟讀幻讀不可重復讀及事務(wù)的隔離級別和MVCC、LBCC實現(xiàn),事務(wù)A?按照查詢條件讀取某個范圍的記錄,其他事務(wù)又在該范圍內(nèi)出入了滿足條件的新記錄,當事務(wù)A再次讀取數(shù)據(jù)到時候我們發(fā)現(xiàn)多了滿足記錄的條數(shù)2022-07-07MySQL字段時間類型該如何選擇實現(xiàn)千萬數(shù)據(jù)下性能提升10%~30%
這篇文章主要介紹了MySQL字段的時間類型該如何選擇?才能實現(xiàn)千萬數(shù)據(jù)下性能提升10%~30%,主要概述datetime、timestamp與整形時間戳相關(guān)的內(nèi)容,并在千萬級別的數(shù)據(jù)量中測試它們的性能,最后總結(jié)出它們的特點與使用場景2023-10-10詳解DBeaver連接MySQL8以上版本以及解決可能遇到的問題
這篇文章主要介紹了DBeaver連接MySQL8以上版本以及解決可能遇到的問題,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-11-11