亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

 更新時間:2021年09月22日 10:34:01   作者:佚名  
InnoDB 事務(wù)鎖,事務(wù)鎖實現(xiàn)起來的代碼卻是又臭又硬的好大一坨,各種細(xì)節(jié),今天小編在這里整理一個源碼閱讀筆記,把那一坨加鎖相關(guān)的代碼提煉出來,感興趣的小伙伴別忘記收藏奧

本文前提:

代碼MySQL 8.0.13

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

1. Lock 與 Latch

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

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

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

lock是存在lock_sys->rec_hash中,每個record lockrec_hash中通過<space_id, page_no, heap_no>來標(biāo)識

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

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

2. Repeatable Read

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

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

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

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

3. Insert加鎖流程

3.1 lock mode

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

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

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

(LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION)

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

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

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


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

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

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

3.2 加鎖流程

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

【對于主鍵索引】:

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

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

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

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

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

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

(4)插入entry

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

【對于二級索引】

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

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

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

判斷record與entry是否相等:

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

  •  如果不是delete mark,說明的確有duplicate,返回DB_DUPLICATE_KEY到上層,然后上層通過看是INSERT ON DUPLICATE KEY UPDATE還是普通INSERT來決定是轉(zhuǎn)成update操作繼續(xù)還是給用戶報錯duplicate
  • 如果是delete mark,則實際沒有duplicate,接著往下走

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

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

(LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION)

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

(5)插入entry

(6)釋放page latch

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

上述流程在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 隱式鎖

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

4. Select 加鎖流程

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

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

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

其他的流程也就沒什么了:

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

總結(jié):

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

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

相關(guān)文章

  • 深入理解MySQL重做日志 redo log

    深入理解MySQL重做日志 redo log

    redo log:被稱為物理日志,記錄的就是最終修改后的按頁面存儲的數(shù)據(jù)頁,直接存數(shù)據(jù)最終的狀態(tài),用于確保事務(wù)的持久性,本文主要介紹了MySQL重做日志 redo log,感興趣的了解一下
    2022-04-04
  • MySQL中varchar和char類型的區(qū)別

    MySQL中varchar和char類型的區(qū)別

    VARCHAR和CHAR是兩種最主要的字符串類型。那么MySQL中varchar和char類型的區(qū)別是什么,本文就具體來介紹一下,感興趣的可以了解一下
    2021-11-11
  • mysqldump你可能不知道的參數(shù)

    mysqldump你可能不知道的參數(shù)

    這篇文章主要介紹了mysqldump你可能不知道的參數(shù),幫助大家更好的理解和使用MySQL,感興趣的朋友可以了解下
    2020-11-11
  • 簡單談?wù)凪ySQL數(shù)據(jù)透視表

    簡單談?wù)凪ySQL數(shù)據(jù)透視表

    這篇文章主要介紹了簡單談?wù)凪ySQL數(shù)據(jù)透視表的相關(guān)資料,需要的朋友可以參考下
    2019-08-08
  • mysql之set與enum的介紹

    mysql之set與enum的介紹

    本篇文章是對mysql中的set與enum進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-06-06
  • MySQL修改root賬號密碼的方法

    MySQL修改root賬號密碼的方法

    這篇文章介紹了兩種情況,知道root密碼的情況下修改root密碼,以及忘記了root密碼,如何對root的密碼進(jìn)行修改,需要的朋友可以參考下
    2015-07-07
  • mysql數(shù)據(jù)遷移之data目錄復(fù)制方法

    mysql數(shù)據(jù)遷移之data目錄復(fù)制方法

    這篇文章主要給大家介紹了關(guān)于mysql數(shù)據(jù)遷移之data目錄復(fù)制方法的相關(guān)資料,MySQL的data文件是存儲數(shù)據(jù)庫的核心文件,它包含了所有的表、索引、視圖和其它相關(guān)的數(shù)據(jù),通過復(fù)制這些文件,我們可以將一個MySQL數(shù)據(jù)庫遷移到另一個地方,需要的朋友可以參考下
    2023-08-08
  • 適合新手的mysql日期類型轉(zhuǎn)換實例教程

    適合新手的mysql日期類型轉(zhuǎn)換實例教程

    Mysql作為一款開元的免費關(guān)系型數(shù)據(jù)庫,用戶基礎(chǔ)非常龐大,下面這篇文章主要給大家介紹了關(guān)于mysql日期類型轉(zhuǎn)換的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-08-08
  • MySQL 超大表快速刪除方式

    MySQL 超大表快速刪除方式

    這篇文章主要介紹了MySQL 超大表快速刪除方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • MySQL 如何限制一張表的記錄數(shù)

    MySQL 如何限制一張表的記錄數(shù)

    能否控制單表在一個固定的記錄數(shù),比如說1W條,超過不讓插入新記錄或者說直接拋出錯誤?關(guān)于這個問題,沒有一個簡化的答案,比如執(zhí)行一條命令或者說簡單設(shè)置一個參數(shù)都不能完美解決。接下來便介紹MySQL 如何限制一張表的記錄數(shù)來給出一些可選解決方案
    2021-09-09

最新評論