人妖在线一区,国产日韩欧美一区二区综合在线,国产啪精品视频网站免费,欧美内射深插日本少妇

新聞動(dòng)態(tài)

MySQL InnoDB 事務(wù)鎖源碼分析

發(fā)布日期:2022-02-06 17:17 | 文章來(lái)源:CSDN

本文前提:

代碼MySQL 8.0.13

只整理Repeatable Read當(dāng)前讀。Read Committed簡(jiǎn)單很多,另外快照讀是基于MVCC不用加鎖,所以不在本文討論范疇。

1. Lock 與 Latch

InnoDB 中的lock是事務(wù)中對(duì)訪問(wèn)/修改的record加的鎖,它一般是在事務(wù)提交或回滾時(shí)釋放。latch是在BTree上定位record的時(shí)候?qū)tree pages加的鎖,它一般是在對(duì)page中對(duì)應(yīng)record加上lock并且完成訪問(wèn)/修改后就釋放,latch的鎖區(qū)間比lock小很多。在具體的實(shí)現(xiàn)中,一個(gè)大的transaction會(huì)被拆成若干小的mini transaction(mtr),如下圖所示:有一個(gè)transaction,依次做了insert,select…for updateupdate操作,這3個(gè)操作分別對(duì)應(yīng)3個(gè)mtr,每個(gè)mtr完成:

  • 在btree查找目標(biāo)record,加相關(guān)page latch;
  • 加目標(biāo)record lock,修改對(duì)應(yīng)record
  • 釋放page latch

為什么要這么做呢?是為了并發(fā),事務(wù)中的每一個(gè)操作,在步驟二完成之后,相應(yīng)的record已經(jīng)加上了lock保護(hù)起來(lái),確保其他并發(fā)事務(wù)無(wú)法修改,所以這時(shí)候沒(méi)必要還占著record所在的page latch,否則其他事務(wù) 訪問(wèn)/修改 相同page的不同record時(shí),這本來(lái)是可以并行做的事情,在這里會(huì)被page latch會(huì)被卡住。

lock是存在lock_sys->rec_hash中,每個(gè)record lockrec_hash中通過(guò)<space_id, page_no, heap_no>來(lái)標(biāo)識(shí)

latch是存在bufferpool對(duì)應(yīng)pageblock中,對(duì)應(yīng)block->lock

本文只關(guān)注lock相關(guān)的東西,latch后面單獨(dú)搞一篇整理

2. Repeatable Read

具體每個(gè)隔離級(jí)別就不展開(kāi)說(shuō)了,這里主要說(shuō)下RR,從名字上也能看出來(lái),RR支持可重復(fù)度,也就是在一個(gè)事務(wù)中,多次執(zhí)行相同的SELECT…FOR UPDATE應(yīng)該看到相同的結(jié)果集(除本事務(wù)修改外),這個(gè)就要求SELECT的區(qū)間里不能有其他事務(wù)插入新的record,所以SELECT除了對(duì)滿足條件的record加lock之外,對(duì)相應(yīng)區(qū)間也要加lock來(lái)保護(hù)起來(lái)。在InnoDB的實(shí)現(xiàn)中,并沒(méi)有一個(gè)一下鎖住某個(gè)指定區(qū)間的鎖,而是把一個(gè)大的區(qū)間鎖拆分放在區(qū)間中已有的多個(gè)record上來(lái)完成。所以引入了Gap lock和Next-key lock的概念,它們加再一個(gè)具體的record上

  • Gap lock 保護(hù)這個(gè)record與其前一個(gè)record之間的開(kāi)區(qū)間
  • Next-key lock 保護(hù)包含這個(gè)record與其前一個(gè)record之間的左開(kāi)右閉區(qū)間

它們都是為了保護(hù)這個(gè)區(qū)間不能被別的事務(wù)插入新的record,實(shí)現(xiàn)RR。

接下來(lái)從源碼實(shí)現(xiàn)上來(lái)分別看下Insert和Select是如何加lock的,結(jié)合著看也就知道InnoDB的RR是如何實(shí)現(xiàn)的了。Insert的加鎖分布在Insert操作的過(guò)程中,遍布在多個(gè)相關(guān)的函數(shù)里,Select的加鎖則比較集中,就在row_search_mvcc里。

3. Insert加鎖流程

3.1 lock mode

lock的mode主要有Share(S)和Exclusive(X)【代碼中對(duì)應(yīng)LOCK_S和LOCK_X】

lock的gap mode主要有Record lock, Gap lock, Next-key lock【代碼中對(duì)應(yīng)LOCK_REC_NOT_GAP, LOCK_GAP, LOCK_ORDINARY】

在具體使用中將 mode|gap_mode 之后就是一個(gè)lock的實(shí)際類(lèi)型,Record lock是作用在單個(gè)record上的記錄鎖,Gap lock/Next-key lock雖然也是加在某個(gè)具體record上,但作用是為了確保record前面的gap不要有其他并發(fā)事務(wù)插入,這個(gè)具體是怎么實(shí)現(xiàn)呢,InnoDB引入了一個(gè)插入意向鎖,他的實(shí)際類(lèi)型是

(LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION)

Gap lock/Next-key lock互斥,如果要插入前檢測(cè)到插入位置的next record上有l(wèi)ock,則會(huì)嘗試對(duì)這個(gè)next record加一個(gè)插入意向鎖,代表本事務(wù)打算給這個(gè)gap里插一個(gè)新record,看行不行?如果已經(jīng)有別的事務(wù)給這里上了Gap/Next-key lock,代表它想保護(hù)這里,所以當(dāng)前插入意向鎖需要等待相關(guān)事務(wù)提交才行。這個(gè)檢測(cè)只是單向的,即插入意向鎖需等待Gap/Next-key lock釋放,而任何鎖不用等待插入意向鎖釋放,否則嚴(yán)重影響這個(gè)gap中不沖突的Insert操作并發(fā)。

具體的鎖沖突檢測(cè)在lock_rec_has_to_wait函數(shù)中,大體原則就是:判斷兩個(gè)lock兼容還是不兼容,首先先做mode的沖突檢測(cè)

如果不沖突,則代表鎖兼容,無(wú)需等待,如果沖突,則接著做gap mode的沖突例外檢測(cè),整理如下:


如果gap mode不沖突,則作為例外情況可以認(rèn)為鎖兼容,無(wú)需等待。可以看到:

  • 插入意向鎖需要等待Gap lockNext-key lock
  • 任何鎖不用等待插入意向鎖
  • Gap lock無(wú)需等待任何鎖
  • Next-key lock需要等待其他Next-key lock及Record Lock,反之亦然

了解了這些鎖兼容原則,接下來(lái)就可以看在實(shí)際Insert流程中是如何使用它們的。

3.2 加鎖流程

Insert的順序是先插入主鍵索引,再依次插入二級(jí)索引。以下是從代碼中整理出來(lái)的流程,插入某個(gè)entry的操作,

【對(duì)于主鍵索引】:

(1)先在查找Btree,加相關(guān)page latch,定位到entry對(duì)應(yīng)插入位置的record (<= entry)

(2)如果要插入的entry已經(jīng)存在,即entry = record,此時(shí)接著判斷:

  • 如果是INSERT ON DUPLICATE KEY UPDATE,則對(duì)recordX Next-key lock
  • 如果是普通INSERT,則對(duì)recordS Next-key lock

之后接著判斷record是否是deleted mark:

  • 如果不是delete mark,說(shuō)明的確有duplicate,返回DB_DUPLICATE_KEY到上層,然后上層通過(guò)看是INSERT ON DUPLICATE KEY UPDATE還是普通INSERT來(lái)決定是轉(zhuǎn)成update操作繼續(xù)還是給用戶(hù)報(bào)錯(cuò)duplicate
  • 如果是deleted mark,則說(shuō)明實(shí)際沒(méi)有duplicate record,接著往下走

(3)判斷record的下一個(gè)record上當(dāng)前有沒(méi)有鎖,如果有的話,則給其加插入意向鎖,確保要插入entry的區(qū)間沒(méi)有其他Gap lock/Next-key lock保護(hù)

(4)插入entry

(5)釋放page latch,此時(shí)依舊占有l(wèi)ock

【對(duì)于二級(jí)索引】

(1)先在查找Btree,加相關(guān)page latch,定位到entry對(duì)應(yīng)插入位置的record (<= entry)

(2)如果要插入的entry已經(jīng)存在,即entry = record,并且當(dāng)前index是unique:

  • 如果是INSERT ON DUPLICATE KEY UPDATE,則對(duì)recordX Next-key lock
  • 如果是普通INSERT,則對(duì)record2S Next-key lock

判斷record與entry是否相等:

如果相等 并且 是普通INSERT,則接著判斷record是否是deleted mark:

  • 如果不是delete mark,說(shuō)明的確有duplicate,返回DB_DUPLICATE_KEY到上層,然后上層通過(guò)看是INSERT ON DUPLICATE KEY UPDATE還是普通INSERT來(lái)決定是轉(zhuǎn)成update操作繼續(xù)還是給用戶(hù)報(bào)錯(cuò)duplicate
  • 如果是delete mark,則實(shí)際沒(méi)有duplicate,接著往下走

(3)如果是INSERT ON DUPLICATE KEY UPDATE 并且 當(dāng)前index是unique,則給其下一個(gè)record X Gap lock,保護(hù)不會(huì)被其他事務(wù)插入相同的entry

(4)判斷record的下一個(gè)record上當(dāng)前有沒(méi)有鎖,如果有的話,則給其加插入意向鎖

(LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION)

確保要插入entry的區(qū)間沒(méi)有其他Gap lock/Next-key lock保護(hù)

(5)插入entry

(6)釋放page latch

:【二級(jí)索引】的步驟3似乎有些多余,因?yàn)榧词褂衅渌l(fā)事務(wù)使用INSERT ON DUPLICATE KEY UPDATE來(lái)插入相同record的話,和【主鍵索引】流程一樣,步驟1也只能串行進(jìn)入,第一個(gè)線程沒(méi)有找到與entry相同的record,走步驟4插入,直到步驟6結(jié)束釋放page latch之后,第二個(gè)線程才能進(jìn)到步驟1里,此時(shí)在步驟2中會(huì)中卡在加record的X Next-key lock上,直到線程一事務(wù)提交之后才能接著進(jìn)行,所以看起來(lái)不會(huì)沖突?

上述流程在row_ins_index_entry函數(shù)中,具體入口如下:

mysql_parse->mysql_execute_command->Sql_cmd_dml::execute->
Sql_cmd_insert_values::execute_inner->write_record->handler::ha_write_row->
ha_innobase::write_row->row_insert_for_mysql->row_insert_for_mysql_using_ins_graph->
row_ins_step->row_ins->row_ins_index_entry_step->row_ins_index_entry

其中插入意向鎖是在lock_rec_insert_check_and_lock函數(shù)里加的,入口如下:

row_ins_index_entry->row_ins_clust_index_entry/row_ins_sec_index_entry->
btr_cur_optimistic_insert/btr_cur_pessimistic_insert->btr_cur_ins_lock_and_undo->
lock_rec_insert_check_and_lock

3.3 隱式鎖

另外要提的一點(diǎn)就是,Insert操作不會(huì)顯式的加鎖,每一條Insert的record上都默認(rèn)有一個(gè)隱式鎖,它是通過(guò)record的隱藏字段trx_id來(lái)檢測(cè)的,對(duì)于主鍵索引,如果要插入的record在Btree中找到,那么只需要通過(guò)比較已有record的trx_id,如果這個(gè)trx_id對(duì)應(yīng)的事務(wù)還是活躍事務(wù),那么說(shuō)明這個(gè)record的插入事務(wù)還未提交,隱式代表這個(gè)record上有鎖,那么此時(shí)就才會(huì)將其轉(zhuǎn)成顯式鎖放進(jìn)lock_sys中并wait,這樣做是為了提高性能,盡量減少對(duì)lock_sys的操作。對(duì)于二級(jí)索引的隱式鎖檢測(cè)就沒(méi)有主鍵索引這么容易了,因?yàn)槎?jí)索引record沒(méi)有記錄trx_id,只能首先通過(guò)其所在page上的max_trx_id與當(dāng)前活躍事務(wù)列表的最小trx_id來(lái)比較,小于它的話代表最后一次修改這個(gè)page的事務(wù)都已經(jīng)提交,所以record上沒(méi)有隱式鎖,如果大于或等于它的話,就需要回主鍵找到對(duì)應(yīng)的主鍵record并遍歷undo歷史版本來(lái)確認(rèn)是否有隱式鎖,具體實(shí)現(xiàn)在row_vers_impl_x_locked_low中,

4. Select 加鎖流程

SELECT做當(dāng)前讀的加鎖流程就在row_search_mvcc當(dāng)中,一條SELECT語(yǔ)句會(huì)多次進(jìn)入這個(gè)函數(shù),第一次是通過(guò)index_read->row_search_mvcc進(jìn)來(lái),一般是首次訪問(wèn)index,取找WHERE里的exact record,之后每次再通過(guò)general_fetch->row_search_mvcc進(jìn)來(lái),根據(jù)具體條件遍歷prev/next record,直到把滿足WHRER條件的record都取出來(lái)。具體的加鎖也就是在訪問(wèn)和遍歷record的過(guò)程中進(jìn)行,row_search_mvcc代碼很長(zhǎng),這里我只提煉總結(jié)下加鎖相關(guān)的流程:

  • 在index上查找search_tuple對(duì)應(yīng)的record。(這里的record可能是上面說(shuō)的index_read進(jìn)來(lái)首次通過(guò)index Btree查找search_tuple對(duì)應(yīng)的record,也有可能是之后多次general_fetch進(jìn)來(lái)通過(guò)之前保存的cursor來(lái)恢復(fù)出來(lái)的上一次訪問(wèn)位置,然后拿到的prev/next record)
  • 如果是index_read 并且 mode是PAGE_CUR_L 或著PAGE_CUR_LE,給定位到的record的next record加 GAP LOCK
  • 如果record是infimum,跳轉(zhuǎn)步驟9 next_rec,如果是supremum,加Next-key Lock,跳轉(zhuǎn)步驟9 next_rec
  • 如果是index_read,record與search_tuple不相等,給recordGAP LOCK,返回 NOT FOUND
  • 到這里說(shuō)明record與search_tuple相等,給record加Next-key Lock,兩個(gè)例外,只加Rec Lock:
  1. 對(duì)于index_read,如果當(dāng)前index是主鍵索引 并且 modePAGE_CUR_GE 并且 search_tuple的fields個(gè)數(shù)等于index的unique fields個(gè)數(shù)
  2. 看是否是unique_search,即search_tuple的fields個(gè)數(shù)等于當(dāng)前index的unique fields個(gè)數(shù) 并且 當(dāng)前index是主鍵索引或者(是二級(jí)索引且search_tuple不包含NULL字段)并且 record不是deleted mark
  • 到這里說(shuō)明加鎖成功了,然后處理record是deleted mark的情況:
  1. 當(dāng)前index是主鍵索引 并且 是unique_search,返回 NOT FOUND
  2. 否則,跳轉(zhuǎn)步驟9 next_rec
  • 如果當(dāng)前index是二級(jí)索引 并且 需要回查主鍵索引,去主鍵索引里找對(duì)應(yīng)的primary record并加 Rec Lock,如果primary record是deleted mark,則當(dāng)前二級(jí)索引接著跳轉(zhuǎn)步驟9 next_rec
  • 成功,返回DB_SUCCESS
  • next_rec: 根據(jù)mode來(lái)取對(duì)應(yīng)的prev/next record,跳轉(zhuǎn) 步驟3 繼續(xù)

重點(diǎn)說(shuō)一下步驟3,這里一般record是infimum或者supremum的情況都是多次genera_fetch對(duì)某個(gè)page取prev/next record之后走到page邊緣,對(duì)于infimum,不會(huì)加任何lock,直接繼續(xù)訪問(wèn)前一個(gè)prev record(即prev page的supremum),對(duì)于supremum的話,會(huì)加上Gap lock,它保護(hù)當(dāng)前page最后一個(gè)user record和next page第一個(gè)user record之間的Gap。

其他的流程也就沒(méi)什么了:

  1. 對(duì)于遍歷到的滿足條件的record,基本默認(rèn)都是加Next-key lock
  2. 二級(jí)索引回表時(shí)只會(huì)對(duì)主鍵加Rec lock
  3. 對(duì)于某些特殊的場(chǎng)景,會(huì)將某些Next-key lock降級(jí)成Rec lock(步驟5)
  4. 還有一些特殊場(chǎng)景,會(huì)只加Gap lock(步驟2、4)

總結(jié):

以上基本就是InnoDB加事務(wù)鎖的相關(guān)流程,InsertSelect的加鎖流程配合著看,事務(wù)鎖的原則及實(shí)現(xiàn)基本也就出來(lái)了。

到此這篇關(guān)于MySQL InnoDB 事務(wù)鎖源碼分析的文章就介紹到這了,更多相關(guān)MySQL InnoDB 事務(wù)鎖源碼分析內(nèi)容請(qǐng)搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!

國(guó)外服務(wù)器租用

版權(quán)聲明:本站文章來(lái)源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來(lái)源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來(lái)源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來(lái),僅供學(xué)習(xí)參考,不代表本站立場(chǎng),如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。

相關(guān)文章

實(shí)時(shí)開(kāi)通

自選配置、實(shí)時(shí)開(kāi)通

免備案

全球線路精選!

全天候客戶(hù)服務(wù)

7x24全年不間斷在線

專(zhuān)屬顧問(wèn)服務(wù)

1對(duì)1客戶(hù)咨詢(xún)顧問(wèn)

在線
客服

在線客服:7*24小時(shí)在線

客服
熱線

400-630-3752
7*24小時(shí)客服服務(wù)熱線

關(guān)注
微信

關(guān)注官方微信
頂部