業務研發團隊 唐蘊夢html
鎖是計算機協調多個進程或線程併發訪問某一資源的機制。node
MySQL的表級鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨佔寫鎖(Table Write Lock)。mysql
事務是指做爲單個邏輯工做單元執行的一系列操做,要麼徹底地執行,要麼徹底地不執行。sql
在默認狀況下,MySQL每執行一條SQL語句,都是一個單獨的事務。數據庫
事務隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
讀未提交(read-uncommitted) | 是 | 是 | 是 |
不可重複讀(read-committed) | 否 | 是 | 是 |
可重複讀(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
mysql默認的事務隔離級別爲repeatable-read服務器
在MVCC併發控制中,讀操做能夠分紅兩類:快照讀 (snapshot read)與當前讀 (current read)。快照讀,讀取的是記錄的可見版本 (有多是歷史版本),不用加鎖。當前讀,讀取的是記錄的最新版本,而且,當前讀返回的記錄,都會加上鎖,保證其餘事務不會再併發修改這條記錄。數據結構
MySQL/InnoDB定義的4種隔離級別:併發
Read Uncommited異步
能夠讀取未提交記錄。
Serializable隔離級別下,讀寫衝突,所以併發度急劇降低,在MySQL/InnoDB下不建議使用。分佈式
MVCC是一種多版本併發控制機制。鎖機制能夠控制併發操做,可是其系統開銷較大,而MVCC能夠在大多數狀況下代替行級鎖,使用MVCC,能下降其系統開銷。
MVCC是經過保存數據在某個時間點的快照來實現的。 不一樣存儲引擎的MVCC、不一樣存儲引擎的MVCC實現是不一樣的,典型的有樂觀併發控制和悲觀併發控制。
InnoDB的MVCC,是經過在每行記錄後面保存兩個隱藏的列來實現的,這兩個列,分別保存了這個行的建立時間,一個保存的是行的刪除時間。這裏存儲的並非實際的時間值,而是系統版本號(能夠理解爲事務的ID),沒開始一個新的事務,系統版本號就會自動遞增,事務開始時刻的系統版本號會做爲事務的ID。IN
SELECT
InnoDB會根據如下兩個條件檢查每行記錄,須要同時知足如下兩個條件:
**注意: 在SELECT時,只知足上述兩個條件也是不能達到快照讀的要求的,好比在RR的隔離級別下會有以下狀況 啓動1號事務、啓動2號事務、1號事務更新x行並提交事務(此時x行的修改版本號爲1,刪除版本號爲未定義)、2號事務讀取x行 安裝如上步驟,若是隻知足上述兩個條件的話,顯然2號事務時能夠讀取到1號事務所作的更新(x行修改版本號爲1知足小於2,刪除版本號爲未定義知足事務開始以前未刪除),顯然是不足夠知足快照讀的要求**
事實上,在讀取到知足上述兩個條件的行時,InnoDB還會進行二次檢查,如上圖所示
活躍事務列表:RC隔離級別下,在語句開始時從全局事務表中獲取活躍(未提交)事務構造Read View,RR隔離級別下,在事務開始時從全局事務表中獲取活躍事務構造Read View
至此,經過上述步驟,能夠實現真正的快照讀。
上述策略的結果就是,在讀取數據的時候,InnoDB幾乎不用得到任何鎖,每一個查詢都經過版本檢查,只得到本身須要的數據版本,從而大大提升了系統的併發度。 這種策略的缺點是,每行記錄都須要額外的存儲空間,更多的行檢查工做和一些額外的維護工做。
上述更新前創建undo log,根據各類策略讀取時非阻塞就是MVCC,undo log中的行就是MVCC中的多版本,這個可能與咱們所理解的MVCC有較大的出入,通常咱們認爲MVCC有下面幾個特色:
就是每行都有版本號,保存時根據版本號決定是否成功,聽起來含有樂觀鎖的味道,而Innodb的實現方式是:
兩者最本質的區別是,當修改數據時是否要排他鎖定,若是鎖定了還算不算是MVCC?
Innodb的實現真算不上MVCC,由於並無實現核心的多版本共存,undo log中的內容只是串行化的結果,記錄了多個事務的過程,不屬於多版本共存。但理想的MVCC是難以實現的,當事務僅修改一行記錄使用理想的MVCC模式是沒有問題的,能夠經過比較版本號進行回滾;但當事務影響到多行數據時,理想的MVCC據無能爲力了。
好比,若是Transaciton1執行理想的MVCC,修改Row1成功,而修改Row2失敗,此時須要回滾Row1,但由於Row1沒有被鎖定,其數據可能又被Transaction2所修改,若是此時回滾Row1的內容,則會破壞Transaction2的修改結果,致使Transaction2違反ACID。
理想MVCC難以實現的根本緣由在於企圖經過樂觀鎖代替二段提交。修改兩行數據,但爲了保證其一致性,與修改兩個分佈式系統中的數據並沒有區別,而二提交是目前這種場景保證一致性的惟一手段。二段提交的本質是鎖定,樂觀鎖的本質是消除鎖定,兩者矛盾,故理想的MVCC難以真正在實際中被應用,Innodb只是借了MVCC這個名字,提供了讀的非阻塞而已。
另外,爲了容許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖。
意向鎖僅僅用於表鎖和行鎖的共存使用。若是咱們的操做僅僅涉及行鎖,那麼意向鎖不會對咱們的操做產生任何影響。在任一操做給表A的一行記錄加鎖前,首先要給該表加意向鎖,若是得到了意向鎖,而後纔會加行鎖,並在加行鎖時判斷是否衝突。若是如今有一個操做要得到表A的表鎖,因爲意向鎖的存在,表鎖獲取會失敗(若是沒有意向鎖的存在,加表鎖以前可能要遍歷整個聚簇索引,判斷是否有行鎖存在,若是沒有行鎖才能加表鎖)。
同理,若是某一操做已經得到了表A的表鎖,那麼另外一操做得到行鎖以前,首先會檢查是否能夠得到意向鎖,並在得到意向鎖失敗後,等待表鎖操做的完成。也就是說:1.意向鎖是表級鎖,可是卻表示事務正在讀或寫某一行記錄;2.意向鎖之間不會衝突, 由於意向鎖僅僅表明要對某行記錄進行操做,在加行鎖時,會判斷是否衝突;3.意向鎖是InnoDB自動加的,不需用戶干預。
InnoDB使用MVCC來實現一致性非鎖定讀,在read-committed和repeatable-read兩種事務隔離級別下使用,且效果不一樣,具體以下。
read-committed
在讀已提交的隔離級別下,事務在一致性非鎖定讀始終讀取當前最新的數據快照,即當其餘事務提交更新後快照更新也會讀取最新的,也就是出現不可重複讀。
repeatable-read
在可重複讀的隔離級別下,事務始終讀取事務開始時的快照版本。
一致性鎖定讀有兩種實現方式,一種是加X鎖,一種是加S鎖
select ... for update 顯示的使用加X鎖的方式讀取
select ... lock in share mode 顯示的使用加S鎖的方式讀取
innodb_autoinc_lock_mode有3種配置模式:0、一、2,
InnoDB行鎖是經過給索引上的索引項加鎖來實現的,這一點MySQL與Oracle不一樣,後者是經過在數據塊中對相應數據行加鎖來實現的。InnoDB這種行鎖實現特色意味着:只有經過索引條件檢索數據,InnoDB才使用行級鎖,不然,InnoDB將使用表鎖!
InnoDB目前處理死鎖的方法是:將持有最少行級排它鎖的事務回滾。若是是由於死鎖引發的回滾,能夠考慮在應用程序中從新執行。
在事務中,若是要更新記錄,應該直接申請足夠級別的鎖,即排他鎖,而不該先申請共享鎖,更新時再申請排他鎖,由於當用戶申請排他鎖時,其餘事務可能又已經得到了相同記錄的共享鎖,從而形成鎖衝突,甚至死鎖。
一般來講,死鎖都是應用設計的問題,經過調整業務流程、數據庫對象設計、事務大小,以及訪問數據庫的SQL語句,絕大部分死鎖均可以免。介紹幾種避免死鎖的經常使用方法。
若是出現死鎖,能夠用SHOW INNODB STATUS命令來肯定最後一個死鎖產生的緣由。返回結果中包括死鎖相關事務的詳細信息,如引起死鎖的SQL語句,事務已經得到的鎖,正在等待什麼鎖,以及被回滾的事務等。
死鎖的發生與否,並不在於事務中有多少條SQL語句,死鎖的關鍵在於:兩個(或以上)的Session加鎖的順序不一致。而使用本文上面提到的,分析MySQL每條SQL語句的加鎖規則,分析出每條語句的加鎖順序,而後檢查多個併發SQL間是否存在以相反的順序加鎖的狀況,就能夠分析出各類潛在的死鎖狀況,也能夠分析出線上死鎖發生的緣由。
問題:
按索引項來加鎖的話,不一樣索引相同行,會不會同時得到不一樣的鎖卻操做同一行
自增鎖,是語句級的鎖,若是當前事務先獲取鎖,卻後執行完,在從庫按語句複製的話,會不會出現ID不一致
lock0types.h 事務鎖系統的類型定義,包含了 lock_mode定義 lock0priv.ic 鎖模塊內部的一些方法,被用於除了lock0lock.cc的三個文件裏, lock_get_type_low 獲取鎖是表鎖仍是行鎖 lock_clust_rec_some_has_impl 檢查一行數據上是否有隱示的x鎖 lock_rec_get_n_bits 獲取一個記錄鎖的鎖位圖的bit數目 lock_rec_set_nth_bit 設置第n個記錄鎖bit位爲true lock_rec_get_next_on_page 獲取當前page上的下一個記錄鎖 lock_rec_get_next_on_page_const lock_rec_get_first_on_page_addr 獲取當前page上第一個記錄鎖 lock_rec_get_first_on_page lock_rec_get_next 返回當前記錄上的下一個顯示鎖請求的鎖 lock_rec_get_next_const lock_rec_get_first 獲取當前記錄上的第一個顯示鎖請求的鎖 lock_rec_get_nth_bit Gets the nth bit of a record lock. lock_get_mode 獲取一個鎖的 lock_mode lock_mode_compatible 判斷兩個lock_mode是否兼容 lock_mode_stronger_or_eq 判斷lock_mode 1 是否比 lock_mode 2更強 lock_get_wait 判斷一個鎖是否是等待鎖 lock_rec_find_similar_on_page 查找一個合適的鎖結構在當前事務當前頁面下???找到的話就不用新建立鎖結構??? lock_table_has 檢查一個事務是否有指定類型的表鎖,只能由當前事務調用 lock0priv.h 鎖模塊內部的結構和方法 struct lock_table_t 表鎖結構 struct lock_rec_t 行鎖結構 struct lock_t 鎖通用結構 static const byte lock_compatibility_matrix[5][5] 鎖的兼容關係 static const byte lock_strength_matrix[5][5] 鎖的強弱關係 enum lock_rec_req_status 記錄鎖請求狀態 struct RecID 記錄鎖ID class RecLock 記錄鎖類 add_to_waitq 入隊一個鎖等待 create 爲事務建立一個鎖並初始化 is_on_row Check of the lock is on m_rec_id. lock_alloc 建立鎖實例 prepare 作一些檢查個預處理爲建立一個記錄鎖 mark_trx_for_rollback 收集須要異步回滾的事務 jump_queue 跳過全部低優先級事務並添加鎖,若是能授予鎖,則授予,不能的話把其餘都標記異步回滾 lock_add 添加一個記錄鎖到事務鎖列表和鎖hash表中 deadlock_check 檢查並解決死鎖 check_deadlock_result 檢查死鎖檢查的結果 is_predicate_lock 返回時不是predictate鎖 init 按照要求設置上下文 lock_get_type_low 返回行鎖仍是表鎖 lock_rec_get_prev 獲取一個記錄上的前一個鎖
在 Innodb 內部用一個 unsiged long 類型數據表示鎖的類型, 最低的 4 個 bit 表示 lock_mode, 5-8 bit 表示 lock_type, 剩下的高位 bit 表示行鎖的類型。
5-8 bit 位標識 lock_type 目前只使用了兩個,第5位標識是表鎖,第6位標識是行鎖
#define LOCK_TABLE 16 /*!< table lock */ //表鎖 #define LOCK_REC 32 /*!< record lock */ //記錄鎖
lock描述了鎖的基本模式,目前有5種模式,IS、IX、S、X、AI
enum lock_mode { LOCK_IS = 0, /* intention shared */ LOCK_IX, /* intention exclusive */ LOCK_S, /* shared */ LOCK_X, /* exclusive */ LOCK_AUTO_INC, /* locks the auto-inc counter of a table in an exclusive mode */ LOCK_NONE, /* this is used elsewhere to note consistent read */ LOCK_NUM = LOCK_NONE, /* number of lock modes */ LOCK_NONE_UNSET = 255 };
如下是鎖的基本模式的兼容關係和強弱關係
/* LOCK COMPATIBILITY MATRIX * IS IX S X AI * IS + + + - + * IX + + - - + * S + - + - - * X - - - - - * AI + + - - - * * Note that for rows, InnoDB only acquires S or X locks. * For tables, InnoDB normally acquires IS or IX locks. * S or X table locks are only acquired for LOCK TABLES. * Auto-increment (AI) locks are needed because of * statement-level MySQL binlog. * See also lock_mode_compatible(). */ static const byte lock_compatibility_matrix[5][5] = { /** IS IX S X AI */ /* IS */ { TRUE, TRUE, TRUE, FALSE, TRUE}, /* IX */ { TRUE, TRUE, FALSE, FALSE, TRUE}, /* S */ { TRUE, FALSE, TRUE, FALSE, FALSE}, /* X */ { FALSE, FALSE, FALSE, FALSE, FALSE}, /* AI */ { TRUE, TRUE, FALSE, FALSE, FALSE} }; /* STRONGER-OR-EQUAL RELATION (mode1=row, mode2=column) * IS IX S X AI * IS + - - - - * IX + + - - - * S + - + - - * X + + + + + * AI - - - - + * See lock_mode_stronger_or_eq(). */ static const byte lock_strength_matrix[5][5] = { /** IS IX S X AI */ /* IS */ { TRUE, FALSE, FALSE, FALSE, FALSE}, /* IX */ { TRUE, TRUE, FALSE, FALSE, FALSE}, /* S */ { TRUE, FALSE, TRUE, FALSE, FALSE}, /* X */ { TRUE, TRUE, TRUE, TRUE, TRUE}, /* AI */ { FALSE, FALSE, FALSE, FALSE, TRUE} };
剩下的高位標識行鎖的模式,對於表鎖這些位都是空的
目前record_lock_type有如下值
#define LOCK_WAIT 256 /*!< Waiting lock flag; when set, it //鎖等待 means that the lock has not yet been granted, it is just waiting for its turn in the wait queue */ /* Precise modes */ #define LOCK_ORDINARY 0 /*!< this flag denotes an ordinary next-key lock in contrast to LOCK_GAP or LOCK_REC_NOT_GAP */ #define LOCK_GAP 512 /*!< when this bit is set, it means that the lock holds only on the gap before the record; for instance, an x-lock on the gap does not give permission to modify the record on which the bit is set; locks of this type are created when records are removed from the index chain of records */ #define LOCK_REC_NOT_GAP 1024 /*!< this bit means that the lock is only on the index record and does NOT block inserts to the gap before the index record; this is used in the case when we retrieve a record with a unique key, and is also used in locking plain SELECTs (not part of UPDATE or DELETE) when the user has set the READ COMMITTED isolation level */ #define LOCK_INSERT_INTENTION 2048 /*!< this bit is set when we place a waiting gap type record lock request in order to let an insert of an index record to wait until there are no conflicting locks by other transactions on the gap; note that this flag remains set when the waiting lock is granted, or if the lock is inherited to a neighboring record */ #define LOCK_PREDICATE 8192 /*!< Predicate lock */ #define LOCK_PRDT_PAGE 16384 /*!< Page lock */
首先是鎖系統結構,在Innodb啓動的時候初始化,在Innodb結束的時候釋放
主要保存着鎖的hash表,以及相關事務、線程的一些信息
/** The lock system struct */ struct lock_sys_t{ char pad1[CACHE_LINE_SIZE]; /*!< padding to prevent other memory update hotspots from residing on the same memory cache line */ LockMutex mutex; /*!< Mutex protecting the locks */ hash_table_t* rec_hash; /*!< hash table of the record locks */ hash_table_t* prdt_hash; /*!< hash table of the predicate lock */ hash_table_t* prdt_page_hash; /*!< hash table of the page lock */ char pad2[CACHE_LINE_SIZE]; /*!< Padding */ LockMutex wait_mutex; /*!< Mutex protecting the next two fields */ srv_slot_t* waiting_threads; /*!< Array of user threads suspended while waiting for locks within InnoDB, protected by the lock_sys->wait_mutex */ srv_slot_t* last_slot; /*!< highest slot ever used in the waiting_threads array, protected by lock_sys->wait_mutex */ ibool rollback_complete; /*!< TRUE if rollback of all recovered transactions is complete. Protected by lock_sys->mutex */ ulint n_lock_max_wait_time; /*!< Max wait time */ os_event_t timeout_event; /*!< Set to the event that is created in the lock wait monitor thread. A value of 0 means the thread is not active */ bool timeout_thread_active; /*!< True if the timeout thread is running */ }; lock_sys_create . Creates the lock system at database start. lock_sys_close Closes the lock system at database shutdown
不管是行鎖仍是表鎖都使用lock_t結構保存,其中用一個union來分別保存行鎖和表鎖不一樣的數據,分別爲lock_table_t和lock_rec_t
struct lock_t { trx_t* trx; /*!< transaction owning the lock */ UT_LIST_NODE_T(lock_t) trx_locks; /*!< list of the locks of the transaction */ dict_index_t* index; /*!< index for a record lock */ lock_t* hash; /*!< hash chain node for a record lock. The link node in a singly linked list, used during hashing. */ union { //表鎖和記錄鎖不一樣的數據 lock_table_t tab_lock;/*!< table lock */ //表鎖結構體 lock_rec_t rec_lock;/*!< record lock */ //記錄鎖結構體 } un_member; /*!< lock details */ ib_uint32_t type_mode; /*!< lock type, mode, LOCK_GAP or LOCK_REC_NOT_GAP, LOCK_INSERT_INTENTION, wait flag, ORed */ //鎖類型 /** Remove GAP lock from a next Key Lock */ void remove_gap_lock() //移除一個next-key鎖的gap鎖 { ut_ad(!is_gap()); ut_ad(!is_insert_intention()); ut_ad(is_record_lock()); type_mode |= LOCK_REC_NOT_GAP; } /** Determine if the lock object is a record lock. @return true if record lock, false otherwise. */ bool is_record_lock() const //判斷是不是記錄鎖 { return(type() == LOCK_REC); } /** Determine if it is predicate lock. @return true if predicate lock, false otherwise. */ bool is_predicate() const { return(type_mode & (LOCK_PREDICATE | LOCK_PRDT_PAGE)); } bool is_waiting() const { return(type_mode & LOCK_WAIT); } bool is_gap() const { return(type_mode & LOCK_GAP); } bool is_record_not_gap() const { return(type_mode & LOCK_REC_NOT_GAP); } bool is_insert_intention() const { return(type_mode & LOCK_INSERT_INTENTION); } ulint type() const { return(type_mode & LOCK_TYPE_MASK); } enum lock_mode mode() const { return(static_cast<enum lock_mode>(type_mode & LOCK_MODE_MASK)); } /** Get lock hash table @return lock hash table */ hash_table_t* hash_table() const { return(lock_hash_get(type_mode)); } /** Get tablespace ID for the lock @return space ID */ ulint space() const { return(un_member.rec_lock.space); } /** Get page number of the lock @return page number */ ulint page_number() const { return(un_member.rec_lock.page_no); } /** Print the lock object into the given output stream. @param[in,out] out the output stream @return the given output stream. */ std::ostream& print(std::ostream& out) const; /** Convert the member 'type_mode' into a human readable string. @return human readable string */ std::string type_mode_string() const; const char* type_string() const { switch (type_mode & LOCK_TYPE_MASK) { case LOCK_REC: return("LOCK_REC"); case LOCK_TABLE: return("LOCK_TABLE"); default: ut_error; } } }; /** A table lock */ struct lock_table_t { dict_table_t* table; /*!< database table in dictionary cache */ UT_LIST_NODE_T(lock_t) locks; /*!< list of locks on the same table */ /** Print the table lock into the given output stream @param[in,out] out the output stream @return the given output stream. */ std::ostream& print(std::ostream& out) const; }; /** Record lock for a page */ struct lock_rec_t { ib_uint32_t space; /*!< space id */ ib_uint32_t page_no; /*!< page number */ ib_uint32_t n_bits; /*!< number of bits in the lock bitmap; NOTE: the lock bitmap is placed immediately after the lock struct */ /** Print the record lock into the given output stream @param[in,out] out the output stream @return the given output stream. */ std::ostream& print(std::ostream& out) const; };
Innodb 使用位圖來表示鎖具體鎖住了那幾行,在函數 lock_rec_create 中爲 lock_t 分配內存空間的時候,會在對象地址後分配一段內存空間(當前行數 + 64)用來保存位圖。n_bits 表示位圖大小。
/* Make lock bitmap bigger by a safety margin */ n_bits = page_dir_get_n_heap(page) + LOCK_PAGE_BITMAP_MARGIN; n_bytes = 1 + n_bits / 8; lock = static_cast<lock_t*>( mem_heap_alloc(trx->lock.lock_heap, sizeof(lock_t) + n_bytes));
explicit lock 顯示鎖
implicit lock 隱示鎖
InnoDB增長隱示鎖的目的是在INSERT的時候不加鎖
具體實現爲
lock system 開始啓動 申請lock_sys_t結構,初始化結構體
lock system 結束關閉 釋放lock_sys_t結構的元素,釋放結構體
https://www.colabug.com/32979...
問題:爲何有GAP也能插入(有GAP是不能插入的),插入意向鎖何時加(插入以前嘗試加插入意向鎖,衝突加等待,不衝突直接插數據), 有什麼用,惟一鍵衝突如何處理的(須要檢測衝突會先嚐試給行加S|next-key lock,加成功再檢測)
和加鎖有關的流程大概以下
刪除加鎖有個重要的問題是,刪除併發的時候的加鎖會有如下死鎖問題
lock_table
主要的參數是 mode(鎖類型),block(包含該行的 buffer 數據頁),heap_no(具體哪一行)。就能夠肯定加什麼樣的鎖,以及在哪一行加。
加鎖流程主要是lock fast和lock slow,首先進入lock fast進行快速加鎖,若是快速加鎖失敗則進入lock slow開始正常加鎖流程,可能有鎖衝突檢查、死鎖檢查等流程
lock_rec_has_expl函數遍歷rec_hash,獲取事務編號是當前事務的鎖,同時知足如下五個條件就斷定爲有更強的鎖
3.若是沒有更強的鎖,調用lock_rec_other_has_conflicting判斷是否有鎖衝突須要等待,若是有轉4,沒有轉5。lock_rec_other_has_conflicting函數遍歷rec_hash,拿出對應行上的每個鎖,調用 lock_rec_has_to_wait 進行衝突判斷
ps:爲何通過2步驟判斷鎖不兼容還須要往下走5個判斷,是由於鎖類型lock_mode/lock_type/rec_lock_type三種標記位同時有,如lock_x|lock_gap, lock_s|lock_rec_not_gap 這兩個鎖雖然lock_mode不兼容,但不衝突
4.調用add_to_waitq,入隊一個鎖等待
5.判斷是不是隱示鎖,是的話直接返回成功,什麼都不作,不是的話調用lock_rec_add_to_queue入隊一個鎖
lock_rec_dequeue_from_page
3.調用lock_rec_grant函數,嘗試給等待鎖加鎖
lock_rec_has_to_wait_in_queue
- 遍歷當前鎖所在頁面的全部非等待鎖 - 對於每一個鎖,根據heap_no判斷當前鎖須要加鎖的行是否被鎖上 - 若是被鎖上,判斷要加的鎖是否須要等待 - 2)對於不須要等待的鎖,調用lock_grant進行加鎖 - 移除lock的lock_wait狀態 - 置空lock的wait_lock - 調用que_thr_end_lock_wait和lock_wait_release_thread_if_suspended喚醒等待中的線程
lock_table_dequeue
1.調用lock_table_remove_low
構造wait-for graph
構造一個有向圖,圖中的節點表明一個事務,圖的一個邊A->B表明着A事務等待B事務的一個鎖
具體實現是在死鎖檢測時,從當前鎖的事務開始搜索,遍歷當前行的全部鎖,判斷當前事務是否須要等待現有鎖釋放,是的話,表明有一條邊,進行一次入棧操做
死鎖檢測
有向圖判斷環,用棧的方式,若是有依賴等待,進行入棧,若是當前事務全部依賴的事務遍歷完畢,進行一次出棧
回滾事務選擇
若是發現循環等待,選擇當前事務和等待的事務其中權重小的一個回滾,具體的權重比較函數是 trx_weight_ge, 若是一個事務修改了不支持事務的表,那麼認爲它的權重較高,不然認爲 undo log 數加持有的鎖數之和較大的權重較高。
DeadlockChecker::search()
等待與喚醒
鎖的等待以及喚醒其實是線程的等待和喚醒,調用函數 lock_wait_suspend_thread 掛起當前線程,配合 OS_EVENT 機制,實現喚醒和鎖超時等功能
分裂
索引頁面分裂致使的鎖分裂
合併
索引頁面合併致使的鎖合併
遷移
插入和刪除記錄時的GAP鎖的遷移
主要是出如今當前事務擁有一個記錄的GAP鎖,又在這個記錄前插入記錄時
set global tx_isolation='repeatable-read'; create table t1(c1 int primary key, c2 int unique) engine=innodb; insert into t1 values(1,1); begin; # supremum 記錄上加 LOCK_X|LOCK_GAP 鎖住(1~) select * from t1 where c2=2 for update; # 發現插入(3,3)的間隙存在GAP鎖,所以給(3,3)加LOCK_X | LOCK_GAP鎖。這樣依然鎖住了(1~) insert into t1 values(3,3);
for (lock = lock_rec_get_first(lock_sys->rec_hash, block, heap_no); lock != NULL; lock = lock_rec_get_next(heap_no, lock)) { //遍歷當前行的全部鎖 if (!lock_rec_get_insert_intention(lock) && (heap_no == PAGE_HEAP_NO_SUPREMUM || !lock_rec_get_rec_not_gap(lock))) { //若是不是插入意向鎖 且 heap是上界或者不是一個非GAP鎖 lock_rec_add_to_queue( //添加一個GAP的且mode和lock一致的鎖到下一行 LOCK_REC | LOCK_GAP | lock_get_mode(lock), block, heir_heap_no, lock->index, lock->trx, FALSE); } }
若有記錄 1 3 5
例子:select * from meng_hinata where id = 10 for update
組合一:id列是主鍵,RC隔離級別
在主鍵id=10列加上X鎖
組合二:id列是二級惟一索引,RC隔離級別
在惟一索引id=10列上加X鎖,在主鍵索引上對應列加X鎖
組合三:id列是二級非惟一索引,RC隔離級別
在二級索引上全部id=10列加上X鎖,這些列對應的主鍵索引列加上X鎖
組合四:id列上沒有索引,RC隔離級別
在聚簇索引上掃描,全部列上加X鎖,此處有個優化,不知足的列在加鎖後,判斷不知足便可釋放鎖,違背二階段加鎖
組合五:id列是主鍵,RR隔離級別
在主鍵id=10列上加X鎖
組合六:id列是二級惟一索引,RR隔離級別
在惟一索引id=10列上加X鎖,在主鍵索引上對應列加X鎖
組合七:id列是二級非惟一索引,RR隔離級別
在二級索引上查找id=10列,找到則加上X鎖和GAP鎖,而後對應的聚簇索引列加上X鎖,最後一個不知足的列只會加上GAP鎖
組合八:id列上沒有索引,RR隔離級別
在聚簇索引上掃描,全部列加上X鎖和GAP鎖
Innodb默認事務隔離級別爲RR
看出默認隔離級別爲Repeatable Read,對user_id = 100000745進行count並顯示加一個X鎖,使用explain看出使用了uid索引即user_id字段的索引
id = 1449731912的數據user_id = 100000745,能夠看出在對user_id字段索引加了X鎖以後,操縱相對應的主鍵索引時也會被阻塞,驗證了對非主鍵索引加X鎖的同時會對相應主鍵索引也加鎖
同時在對user_id字段索引加了X鎖以後,也不能插入user_id相同的新數據,驗證了innodb再RR隔離級別下也是防止了幻讀的現象,實際上當範圍查找時也會再加一個間隙鎖來保證不會有幻讀
能夠看出不使用for update以後變爲非當前讀,也沒有進行加鎖,能夠進行插入操做。
以上兩個圖設置隔離級別爲RC後,能夠看出在user_id = 100000745 進行for update查詢後,仍是能進行插入相同user_id值的列,說明只加了X鎖並無加間隙鎖,同時由於X鎖的緣由,不能進行刪除,驗證了innodb引擎在RC隔離級別對於當前度也是會對讀到的信息加鎖,但沒加間隙鎖,會出現幻讀。
以上兩個圖,在RC隔離級別下。
首先事務A對無索引的列進行查找並加鎖,會掃描全表,注意並無加表鎖,而是對全部行都加了X鎖,可是沒加間隙鎖,事務B仍是能夠插入。
此時事務A再掃描全表並加X鎖,發現被阻塞,提交事務B後繼續運行
事務A對全表加X鎖後,事務B再次嘗試插入,一樣成功,但沒法刪除數據,說明仍是沒用表鎖,對全部行加了X鎖。
InnoDB條件變量核心數據結構爲os_event_t,相似pthread_cont_t。若是須要建立和銷燬則分別使用os_event_create和os_event_free函數。須要等待某個條件變量,先調用os_event_reset,而後使用os_event_wait,若是須要超時等待,使用os_event_wait_time替換os_event_wait便可
InnoDB自旋互斥鎖的實現主要在文件 sync0sync.cc 和sync0sync.ic 中,頭文件sync0sync.h 定義了核心數據結構ib_mutex_t。使用方法很簡單,mutex_create建立鎖,mutex_free釋放鎖,mutex_enter嘗試得到鎖,若是已經被佔用了,則等待。mutex_exit釋放鎖,同時喚醒全部等待的線程,拿到鎖的線程開始執行,其他線程繼續等待。
InnoDB讀寫鎖的核心實如今源文件sync0rw.cc 和sync0rw.ic 中,核心數據結構rw_lock_t 定義在sync0rw.h 中。使用方法與InnoDB 自旋互斥鎖很相似,只不過讀請求和寫請求要調用不一樣的函數。加讀鎖調用rw_lock_s_lock, 加寫鎖調用rw_lock_x_lock,釋放讀鎖調用rw_lock_s_unlock, 釋放寫鎖調用rw_lock_x_unlock,建立讀寫鎖調用rw_lock_create,釋放讀寫鎖調用rw_lock_free。函數rw_lock_x_lock_nowait和rw_lock_s_lock_nowait表示,當加讀寫鎖失敗的時候,直接返回,而不是自旋等待。