目錄
- 1、關于鎖的一些零碎知識,需要熟知
- 事務加鎖方式:
- Innodb事務隔離
- MVCC多版本并發控制
- 常用語句 與 鎖的關系
- 意向鎖
- 行級鎖
- 2、鎖的內存結構以及一些解釋
- 3、InnoDB的鎖代碼實現
- 鎖系統結構lock_sys_t
- lock_t 、lock_rec_t 、lock_table_t
- bitmap
- 鎖的基本模式的兼容關系和強弱關系
- 行鎖類別代碼
- 記錄鎖的alloc函數
- 記錄鎖的add函數
- 記錄鎖的create函數
- 4、鎖的流程
- 表鎖加鎖流程
- 行鎖加鎖流程
- 插入加鎖流程
- 刪除加鎖流程帶來的死鎖
- 釋放鎖流程
- 死鎖流程
- 5、參考
1、關于鎖的一些零碎知識,需要熟知
事務加鎖方式:
兩階段鎖:
整個事務分為兩個階段,前一個階段加鎖,后一個階段為解鎖。在加鎖階段,事務只能加鎖,也可以操作數據,但是不能解鎖,直到事務釋放第一個鎖,就進入了解鎖階段,此階段事務只能解鎖,也可以操作數據,不能再加鎖。
兩階段協議使得事務具有比較高的并發度,因為解鎖不必發生在事務結尾。
不過它沒有解決死鎖問題,因為它在加鎖階段沒有順序要求,如果兩個事務分別申請了A,B鎖,接著又申請對方的鎖,此時進入死鎖狀態。
Innodb事務隔離
在MVCC并發控制中,讀操作可以分為兩類:快照讀和當前讀。
快照讀讀取的是記錄的可見版本(有可能是歷史版本),不用加鎖。
當前讀,讀取的是記錄的最新版本,并且當前讀返回的記錄都會加上鎖,保證其他事務不再會并發修改這條記錄。
- Read Uncommited:可以讀未提交記錄
- Read Committed(RC):當前讀操作保證對獨到的記錄加鎖,存在幻讀現象。使用MVCC,但是讀取數據時讀取自身版本和最新版本,以最新為主,可以讀已提交記錄,存在不可重復
- Repeatable Read(RR):當前讀操作保證對讀到的記錄加鎖,同時保證對讀取的范圍加鎖,新的滿足查詢條件的記錄不能夠插入(間隙鎖),不存在幻讀現象。使用MVCC保存兩個事務操作的數據互相隔離,不存在不可重復讀現象。
- Serializable:MVCC并發控制退化為基于鎖的并發控制。不區分快照讀和當前讀,所有讀操作均為當前讀,讀加S鎖,寫加X鎖。
MVCC多版本并發控制
MVCC是一種多版本并發控制機制。鎖機制可以控制并發操作,但是其系統開銷較大,而MVCC可以在大多數情況下替代行級鎖,降低系統開銷。
MVCC是通過保存數據在某個時間點的快照來實現的,典型的有樂觀并發控制和悲觀并發控制。
InnoDB的MVCC,是通過在每行記錄后面保存兩個隱藏的列來實現的,這兩個列,分別保存這個行的創建時間和刪除時間,這里存儲的并不是實際的時間值,而是版本號,可以理解為事務的ID。每開始一個新的事務,這個版本號就會自動遞增。
對于幾種的操作:
- INSERT:為新插入的每一行保存當前版本號作為版本號
- UPDATE:新插入一行記錄,并且保存其創建時間為當前事務ID,同時保存當前
- DELETE:為刪除的每一行保存當前版本號作為版本號
- SELECT:
- InnoDB只會查找版本號小于等于事務系統版本號
- 行的刪除版本要么未定義要么大于當前事務版本號,這樣可以確保事務讀取的行,在事務開始刪除前未被刪除
事實上,在讀取滿足上述兩個條件的行時,InnoDB還會進行二次檢查。
活躍事務列表:RC隔離級別下,在語句開始時從全局事務表中獲取活躍(未提交)事務構造Read View,RR隔離級別下,事務開始時從全局事務表獲取活躍事務構造Read View:
1、取當前行的修改事務ID,和Read View中的事務ID做比較,若小于最小的ID或小于最大ID但不在列表中,轉2步驟。若是大于最大ID,轉3
2、若進入此步驟,可說明,最后更新當前行的事務,在構造Read View時已經提交,返回當前行數據
3、若進入此步驟,可說明,最后更新當前行的事務,在構造Read View時還未創建或者還未提交,取undo log中記錄的事務ID,重新進入步驟1.
根據上面策略,在讀取數據的時候,InnoDB幾乎不用獲得任何鎖,每個查詢都能通過版本查詢,只獲得自己需要的數據版本,從而大大提高了系統并發度。
缺點是:每行記錄都需要額外的存儲空間,更多的行檢查工作,額外的維護工作。
一般我們認為MVCC有幾個特點:
- 每個數據都存在一個版本,每次數據更新時都更新該版本
- 修改時copy出當前版本修改,各個事務之間沒有干擾
- 保存時比較版本號,如果成功,則覆蓋原記錄;失敗則rollback
看上去保存是根據版本號決定是否成功,有點樂觀鎖意味,但是Innodb實現方式是:
- 事務以排他鎖的形式修改原始數據
- 把修改前的數據存放于undo log,通過回滾指針與主數據關聯
- 修改成功后啥都不做,失敗則恢復undo log中的數據。
innodb沒有實現MVCC核心的多版本共存,undo log內容只是串行化的結果,記錄了多個事務的過程,不屬于多版本共存。當事務影響到多行數據,理想的MVCC無能為力。
如:事務1執行理想MVCC,修改row1成功,修改row2失敗,此時需要回滾row1,但是由于row1沒有被鎖定,其數據可能又被事務2修改,如果此時回滾row1內容,會破壞事務2的修改結果,導致事務2違反ACID。
理想的MVCC難以實現的根本原因在于企圖通過樂觀鎖代替二階段提交。修改兩行數據,但為了保證其一致性,與修改兩個分布式系統數據并無區別,而二階段提交是目前這種場景保證一致性的唯一手段。二階段提交的本質是鎖定,樂觀鎖的本質是消除鎖定,二者矛盾。innodb只是借了MVCC名字,提供了讀的非阻塞。
采用MVCC方式,讀-寫操作彼此并不沖突,性能更高;如果采用加鎖方式,讀-寫操作彼此需要排隊執行,從而影響性能。一般情況下,我們更愿意使用MVCC來解決讀-寫操作并發執行的問題,但是在一些特殊業務場景中,要求必須采用加鎖的方式執行。
常用語句 與 鎖的關系
對讀取的記錄加S鎖:
select ... lock in share mode;
對讀取的記錄加X鎖:
select ... for update;
delete:
對一條語句執行delete,先在B+樹中定位到這條記錄位置,然后獲取這條記錄的X鎖,最后執行delete mark操作。
update:
- 如果未修改該記錄鍵值并且被更新的列所占用的存儲空間在修改前后未發生變化,則現在B+樹定位到這條記錄的位置,然后再獲取記錄的X鎖,最后在原記錄的位置進行修改操作。
- 如果為修改該記錄的鍵值并且至少有一個被更新的列占用的存儲空間在修改后發生變化,則先在B+樹中定位到這條記錄的位置,然后獲取記錄的X鎖,然后將原記錄刪除,再重新插入一個新的記錄。
- 如果修改了該記錄的鍵值,則相當于在原記錄上執行delete操作之后再來一次insert操作。
insert:
新插入的一條記錄收到隱式鎖保護,不需要在內存中為其生成對應的鎖結構。
意向鎖
為了允許行鎖和表鎖共存,實現多粒度鎖機制。InnoDB還有兩種內部使用的意向鎖,兩種意向鎖都是表鎖。
意向共享鎖(IS):事務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖
意向排他鎖(IX):事務打算給數據行加排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖。
意向鎖僅僅用于表鎖和行鎖的共存使用。它們的提出僅僅是為了在之后加表級S鎖或者X鎖是可以快速判斷表中的記錄是否被上鎖,以避免用遍歷的方式來查看表中有沒有上鎖的記錄。
需要注意的三點:
1、意向鎖是表級鎖,但是卻表示事務正在讀或寫某一行記錄
2、意向鎖之間不會沖突,因為意向鎖僅僅代表對某行記錄進行操作,在加行鎖的時候會判斷是否沖突
3、意向鎖是InnoDB自動加的,不需要用戶干預。
行級鎖
-
Record Lock:就是普通的行鎖,官方名稱:
LOCK_REC_NOT_GAP
,用來鎖住在聚集索引上的一條行記錄 -
Gap Lock:用來在可重復讀隔離級別下解決幻讀現象。已知幻讀還有一種方法解決:MVCC,還一種就是加鎖。但是在使用加鎖方案時有個問題,事務在第一次執行讀取操作時,“幻影記錄”還沒有插入,所以我們無法給“幻影記錄”加上Record Lock。InnoDB提出了Gap鎖,官方名稱:
LOCK_GAP
,若一條記錄的numberl列為8,前一行記錄number列為3,我們在這個記錄上加上gap鎖,意味著不允許別的事務在number值為(3,8)區間插入記錄。只有gap鎖的事務提交后將gap鎖釋放掉后,其他事務才能繼續插入。注意:gap鎖只是用來防止插入幻影記錄的,共享gap和獨占gap起到作用相同。對一條記錄加了gap鎖不會限制其他事務對這條記錄加Record Lock或者繼續加gap鎖。另外對于向限制記錄后面的區間的話,可以使用Supremum表示該頁面中最大記錄。
-
Next-Key Lock:當我們既想鎖住某條記錄,又想阻止其他事務在該記錄前面的間隙插入新記錄,使用該鎖。官方名稱:
LOCK_ORDINARY
,本質上就是上面兩種鎖的結合。 -
Insert Intention Lock:一個事務在插入一條你記錄時需要判斷該區間點上是否存在gap鎖或Next-Key Lock,如果有的話,插入就需要阻塞。設計者規定,事務在等待時也需要在內存中生成一個鎖結構,表明有個事務想在某個間隙中插入記錄,但是處于等待狀態。這種狀態鎖稱為Insert Intention Lock,官方名稱:
LOCK_INSERT_INTENTION
,也可以稱為插入意向鎖。
2、鎖的內存結構以及一些解釋
一個事務對多條記錄加鎖時不一定就要創建多個鎖結構。如果符合下面條件的記錄的鎖可以放到一個鎖結構中:
- 在同一個事務中進行加鎖操作
- 被加鎖的記錄在同一個頁面中
- 加鎖的類型是一樣的
- 等待狀態是一樣的
type_mode
是一個32位比特的數,被分為lock_mode
、lock_type
、rec_lock_type
三個部分。
低4位表示:lock_mode
,鎖的模式
0:表示IS鎖
1:表示IX鎖
2:表示S鎖
3:表示X鎖
4:表示AI鎖,就是auto-inc,自增鎖
第5~8位表示:lock_type
,鎖的類型
LOCK_TABLE:第5位為1,表示表級鎖
LOCK_REC:第6位為1,表示行級鎖
其余高位表示:rec_lock_type
,表示行鎖的具體類型,只有lock_type
的值為LOCK_REC時,才會出現細分
LOCK_ORDINARY:為0,表示next-key鎖
LOCK_GAP:為512,即當第10位設置為1時,表示gap鎖
LOCK_REC_NOT_GAP:為1024,當第11位設置為1,表示正常記錄鎖
LOCK_INSERT_INTENTION:為2048,當第12位設置為1時,表示插入意向鎖
LOCK_WAIT:為256,當第9位設置為1時,表示is_waiting為false,表明當前事務獲取鎖成功。
一堆比特位
其他信息:涉及了一些哈希表和鏈表
更加細節的結構可以看這一張圖:
3、InnoDB的鎖代碼實現
鎖系統結構lock_sys_t
詳細講解見:https://dev.mysql.com/doc/dev/mysql-server/latest/structlock__sys__t.html#details
鎖系統結構,在innodb啟動的時候初始化,在innodb結束時釋放。保存鎖的hash表,相關事務、線程的一些信息
/** The lock system struct */
struct lock_sys_t {/** The latches protecting queues of record and table locks */locksys::Latches latches;/** The hash table of the record (LOCK_REC) locks, except for predicate(LOCK_PREDICATE) and predicate page (LOCK_PRDT_PAGE) locks */hash_table_t *rec_hash;/** The hash table of predicate (LOCK_PREDICATE) locks */hash_table_t *prdt_hash;/** The hash table of the predicate page (LOCK_PRD_PAGE) locks */hash_table_t *prdt_page_hash;/** Padding to avoid false sharing of wait_mutex field */char pad2[ut::INNODB_CACHE_LINE_SIZE];/** The mutex protecting the next two fields */Lock_mutex wait_mutex;/** Array of user threads suspended while waiting for locks within InnoDB.Protected by the lock_sys->wait_mutex. */srv_slot_t *waiting_threads;/** The highest slot ever used in the waiting_threads array.Protected by lock_sys->wait_mutex. */srv_slot_t *last_slot;/** TRUE if rollback of all recovered transactions is complete.Protected by exclusive global lock_sys latch. */bool rollback_complete;/** Max lock wait time observed, for innodb_row_lock_time_max reporting. */ulint n_lock_max_wait_time;/** Set to the event that is created in the lock wait monitor thread. A valueof 0 means the thread is not active */os_event_t timeout_event;#ifdef UNIV_DEBUG/** Lock timestamp counter, used to assign lock->m_seq on creation. */std::atomic<uint64_t> m_seq;
#endif /* UNIV_DEBUG */
};
lock_t 、lock_rec_t 、lock_table_t
無論是行鎖還是表鎖都使用lock_t結構保存,其中用一個union來分別保存行鎖和表鎖不同的數據,分別為lock_table_t和lock_rec_t
/** Lock struct; protected by lock_sys latches */
struct lock_t {/** transaction owning the lock */trx_t *trx;/** list of the locks of the transaction */UT_LIST_NODE_T(lock_t) trx_locks;/** Index for a record lock */dict_index_t *index;/** Hash chain node for a record lock. The link node in a singlylinked list, used by the hash table. */lock_t *hash;union {/** Table lock */lock_table_t tab_lock;/** Record lock */lock_rec_t rec_lock;};/** Record lock for a page */
struct lock_rec_t {/** The id of the page on which records referenced by this lock's bitmap arelocated. */page_id_t page_id;/** number of bits in the lock bitmap;NOTE: the lock bitmap is placed immediately after the lock struct */uint32_t n_bits;/** 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;
};struct lock_table_t {dict_table_t *table; /*!< database table in dictionarycache */UT_LIST_NODE_T(lock_t)locks; /*!< list of locks on the sametable *//** 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;
};
bitmap
Innodb 使用位圖來表示鎖具體鎖住了那幾行,在函數 lock_rec_create 中為 lock_t 分配內存空間的時候,會在對象地址后分配一段內存空間(當前行數 + 64)用來保存位圖。n_bits 表示位圖大小。
鎖的基本模式的兼容關系和強弱關系
/* 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}type_mode
};/* 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}
};
行鎖類別代碼
#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 */
記錄鎖的alloc函數
Create the lock instance,創建一個lock實例,在create函數中被調用。主要就是分配一些內存,還有設置事務請求記錄鎖、鎖的索引號、鎖的模式、行鎖的pageid、n_bits。
/**
Create the lock instance
@param[in, out] trx The transaction requesting the lock
@param[in, out] index Index on which record lock is required
@param[in] mode The lock mode desired
@param[in] rec_id The record id
@param[in] size Size of the lock + bitmap requested
@return a record lock instance */
lock_t *RecLock::lock_alloc(trx_t *trx, dict_index_t *index, ulint mode,const RecID &rec_id, ulint size) {ut_ad(locksys::owns_page_shard(rec_id.get_page_id()));/* We are about to modify structures in trx->lock which needs trx->mutex */ut_ad(trx_mutex_own(trx));lock_t *lock;if (trx->lock.rec_cached >= trx->lock.rec_pool.size() ||sizeof(*lock) + size > REC_LOCK_SIZE) {ulint n_bytes = size + sizeof(*lock);mem_heap_t *heap = trx->lock.lock_heap;lock = reinterpret_cast<lock_t *>(mem_heap_alloc(heap, n_bytes));} else {lock = trx->lock.rec_pool[trx->lock.rec_cached];++trx->lock.rec_cached;}lock->trx = trx;lock->index = index;/* Note the creation timestamp */ut_d(lock->m_seq = lock_sys->m_seq.fetch_add(1));/* Setup the lock attributes */lock->type_mode = LOCK_REC | (mode & ~LOCK_TYPE_MASK);lock_rec_t &rec_lock = lock->rec_lock;/* Predicate lock always on INFIMUM (0) */if (is_predicate_lock(mode)) {rec_lock.n_bits = 8;memset(&lock[1], 0x0, 1);} else {ut_ad(8 * size < UINT32_MAX);rec_lock.n_bits = static_cast<uint32_t>(8 * size);memset(&lock[1], 0x0, size);}rec_lock.page_id = rec_id.get_page_id();/* Set the bit corresponding to rec */lock_rec_set_nth_bit(lock, rec_id.m_heap_no);MONITOR_INC(MONITOR_NUM_RECLOCK);MONITOR_INC(MONITOR_RECLOCK_CREATED);return (lock);
}
記錄鎖的add函數
將鎖添加到記錄鎖哈希和事務的鎖列表中。
void RecLock::lock_add(lock_t *lock) {ut_ad((lock->type_mode | LOCK_REC) == (m_mode | LOCK_REC));ut_ad(m_rec_id.matches(lock));ut_ad(locksys::owns_page_shard(m_rec_id.get_page_id()));ut_ad(locksys::owns_page_shard(lock->rec_lock.page_id));ut_ad(trx_mutex_own(lock->trx));bool wait = m_mode & LOCK_WAIT;hash_table_t *lock_hash = lock_hash_get(m_mode);lock->index->table->n_rec_locks.fetch_add(1, std::memory_order_relaxed);if (!wait) {lock_rec_insert_to_granted(lock_hash, lock, m_rec_id);} else {lock_rec_insert_to_waiting(lock_hash, lock, m_rec_id);}#ifdef HAVE_PSI_THREAD_INTERFACE
#ifdef HAVE_PSI_DATA_LOCK_INTERFACE/* The performance schema THREAD_ID and EVENT_ID are used onlywhen DATA_LOCKS are exposed. */PSI_THREAD_CALL(get_current_thread_event_id)(&lock->m_psi_internal_thread_id, &lock->m_psi_event_id);
#endif /* HAVE_PSI_DATA_LOCK_INTERFACE */
#endif /* HAVE_PSI_THREAD_INTERFACE */locksys::add_to_trx_locks(lock);if (wait) {lock_set_lock_and_trx_wait(lock);}
}
記錄鎖的create函數
就是調用alloc,然后add加鎖,
Create a lock for a transaction and initialise it.
@param[in, out] trx Transaction requesting the new lock
@param[in] prdt Predicate lock (optional)
@return new lock instance */
lock_t *RecLock::create(trx_t *trx, const lock_prdt_t *prdt) {ut_ad(locksys::owns_page_shard(m_rec_id.get_page_id()));/* Ensure that another transaction doesn't access the trxlock state and lock data structures while we are adding thelock and changing the transaction state to LOCK_WAIT.In particular it protects the lock_alloc which uses trx's private pool oflock structures.It might be the case that we already hold trx->mutex because we got here from:- lock_rec_convert_impl_to_expl_for_trx- add_to_waitq*/ut_ad(trx_mutex_own(trx));/* Create the explicit lock instance and initialise it. */lock_t *lock = lock_alloc(trx, m_index, m_mode, m_rec_id, m_size);#ifdef UNIV_DEBUG/* GAP lock shouldn't be taken on DD tables with some exceptions */if (m_index->table->is_dd_table &&strstr(m_index->table->name.m_name,"mysql/st_spatial_reference_systems") == nullptr &&strstr(m_index->table->name.m_name, "mysql/innodb_table_stats") ==nullptr &&strstr(m_index->table->name.m_name, "mysql/innodb_index_stats") ==nullptr &&strstr(m_index->table->name.m_name, "mysql/table_stats") == nullptr &&strstr(m_index->table->name.m_name, "mysql/index_stats") == nullptr) {ut_ad(lock_rec_get_rec_not_gap(lock));}
#endif /* UNIV_DEBUG */if (prdt != nullptr && (m_mode & LOCK_PREDICATE)) {lock_prdt_set_prdt(lock, prdt);}lock_add(lock);return (lock);
}
4、鎖的流程
lock system 開始啟動 申請lock_sys_t結構,初始化結構體
lock system 結束關閉 釋放lock_sys_t結構的元素,釋放結構體
表鎖加鎖流程
1、檢查當前事務是否擁有更強的表鎖,如果有的話直接返回成功,否則繼續往下走2、遍歷表的鎖列表,判斷是否有沖突的鎖,沒有轉3,有轉43、直接創建一個表鎖,放入事務的lock list中,放入table 的lock list中,加鎖成功4、創建等待的表鎖,然后進行死鎖檢測和死鎖解決,回滾當前事務或者掛起當前事務
行鎖加鎖流程
插入加鎖流程
1、對表加IX鎖2、對修改的頁面加X鎖3、如果需要檢測唯一鍵沖突,嘗試給需要加的唯一鍵加一個S | next-key lock。可能會產生鎖等待4、判斷是否插入意向鎖沖突,沖突的話加等待的插入意向鎖,不沖突直接插入數據5、釋放頁面鎖
刪除加鎖流程帶來的死鎖
刪除加鎖有個問題,刪除并發的時候的加鎖會導致死鎖。
1、事務1獲取表IX鎖2、事務1獲取頁面X鎖3、事務1獲取第n行的 x | not gap鎖4、事務1刪除第n行5、事務1釋放頁面X鎖6、事務2獲取頁面X鎖7、事務2嘗試獲取第n行的 x | not gap鎖,發現沖突,等待8、事務2釋放頁面X鎖9、事務1釋放第n行的鎖,提交事務10、釋放第n行鎖的時候,檢查到事務2有一個等待鎖,發現可以加鎖了,喚醒事務2,成功加鎖11、事務3獲取頁面X鎖12、事務3嘗試刪除第n行,發現第n行已經被刪除,嘗試獲取第n行的next-key lock,發現事務2有個 x| gap鎖沖突,等待13、事務3釋放頁面X鎖14、事務2獲取頁面X鎖,檢查頁面是否改動,重新檢查第n行數據,發現被刪,嘗試獲取該行next- key lock,發現事務3在等待這個鎖,事務2沖突,進入等待15、造成死鎖
釋放鎖流程
死鎖流程
構造wait-for graph
構造一個有向圖,圖中的節點代表一個事務,圖的一個邊A->B代表著A事務等待B事務的一個鎖
具體實現是在死鎖檢測時,從當前鎖的事務開始搜索,遍歷當前行的所有鎖,判斷當前事務是否需要等待現有鎖釋放,是的話,代表有一條邊,進行一次入棧操作
死鎖檢測
有向圖判斷環,用棧的方式,如果有依賴等待,進行入棧,如果當前事務所有依賴的事務遍歷完畢,進行一次出棧
回滾事務選擇
如果發現循環等待,選擇當前事務和等待的事務其中權重小的一個回滾,具體的權重比較函數是 trx_weight_ge, 如果一個事務修改了不支持事務的表,那么認為它的權重較高,否則認為 undo log 數加持有的鎖數之和較大的權重較高。
5、參考
1、https://segmentfault.com/a/1190000017076101?utm_source=coffeephp.com
2、Mysql 8.022源代碼
3、深入淺出MySQL 8.0 lock_sys鎖相關優化