官方文檔:html
innodb事務模型和鎖5.7mysql
查詢事務的鎖定(能夠查看事務存在的意圖鎖,記錄鎖)
show engine innodb status
session
查詢當前會話的事務級別
select @@transaction_isolation;
spa
查詢全局事務級別
select @@gloabal.transaction_isolation;
code
設置當前會話的事務級別
set session transaction isolation level repeatable read;
orm
鎖定讀取(只有在禁用自動提交時才能set autocommit=0)htm
設置共享模式鎖定(給select加讀鎖)
SELECT ... LOCK IN SHARE MODE
改要等待本事務提交。若是其餘事務已經修改了要讀取的行可是沒有提交,則本事務等待其餘事務結束,並獲取最新的值索引
查詢索引鎖定(給select加寫鎖)
SELECT ... FOR UPDATE
搜索到的索引記錄,鎖定行/索引條目,阻止其餘事務更新這些行,設置共享模式鎖定,或者讀取行事務
事務T1在row行上有共享鎖
一、事務T2申請共享鎖可當即得到,T1, T2同時擁有共享鎖
二、事務T2申請排他鎖須要等待T1釋放
事務T1在row行上有排他鎖
一、事務T2申請共享鎖 or 排他鎖,均需等待T1釋放
innodb支持多粒度鎖:即支持同時存在行鎖和標鎖。爲了支持這個機制,有了意圖鎖
目的:表名事務以後須要在一給表的某行中須要共享 or 排他鎖
意圖共享鎖IS:事務打算在一張表的個別行申請共享鎖(或正擁有)(select ... for share)
意圖排他鎖IX:事務打算再一張表的個別行申請排他鎖(或正擁有)(select ... for update)
事務須要申請共享行鎖以前,先要申請表的意圖共享鎖或意圖排他鎖
是無須要申請排他航所以前,先要申請表的意圖排他鎖
X | IX | S | IS | |
---|---|---|---|---|
X | 互斥 | 互斥 | 互斥 | 互斥 |
IX | 互斥 | 兼容 | 互斥 | 兼容 |
S | 互斥 | 互斥 | 兼容 | 兼容 |
IS | 互斥 | 兼容 | 兼容 | 兼容 |
(意圖鎖之間互相兼容,其他遵循X,S互斥原則)
意圖鎖不會阻止除了完整表請求以外的任何(如何lock tables ... write)
某個事務上的某個意圖鎖SHOW ENGINE INNODB STATUS:
TABLE LOCK table test.t trx id 10080 lock mode IX
SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE
給c1=10的行上排他鎖,若是查詢用的是彙集索引,就會使用此排他記錄鎖
SHOW ENGINE INNODB STATUS
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t` trx id 10078 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 8000000a; asc ;; 1: len 6; hex 00000000274f; asc 'O;; 2: len 7; hex b60000019d0110; asc ;;
SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
給10~20之間的行上排他間隙鎖,阻止其餘事務更新
間隙鎖在部分隔離級別下使用
在惟一索引上的查詢(不包括多個col的惟一索引,搜索條件爲某些列),不使用間隙鎖。例:SELECT * FROM child WHERE id = 100;
只有記錄鎖,沒有間隙鎖。若是id沒有索引或者不是惟一索引,會在100以前的全部行上間隙鎖
注意:不一樣的事務能夠在一個間隙上擁有不一樣的間隙鎖。例如:事務A在一個間隙上擁有共享間隙鎖,同時事務B在同一間隙上擁有排他間隙鎖(不管是間隙S鎖仍是間隙X鎖)。這個機制被容許存在是由於,一個記錄被刪除時,會和並不一樣事務在這個記錄上的間隙鎖
間隙鎖惟一的做用就是阻止其餘事務在間隙上的更改,一個事務在一個記錄上有間隙鎖不阻止其餘事務在此間隙上得到間隙鎖
在讀提交隔離級別上,間隙鎖不被使用,除非在進行外鍵約束檢查和重複值檢查時。在讀提交隔離級別上,找不到行時,記錄鎖被釋放。
這個鎖是索引數據上的記錄鎖和索引數據以前的間隙鎖的結合
InnoDB以這樣的方式執行行級鎖定:當它搜索或掃描表索引時,它會在遇到的索引記錄上設置共享鎖或排它鎖。所以,行級鎖其實是索引記錄鎖。
在索引記錄上的next-key lock依然影響在索引記錄以前的間隙鎖。因此,next-key lock是一個索引記錄鎖+在這個索引數據以前的間隙鎖。若是一個會話有記錄R索引上的共享/排他鎖,其餘會話不能夠插入一個新的索引記錄在這個R索引以前
假設10,11,13,20上有索引,可能的next-key lock以下,最後一個間隔,next-key lock鎖定最大索引值以後的間隙
(negative infinity, 10] (10, 11] (11, 13] (13, 20] (20, positive infinity)
innodb在可重複度級別下,使用next-key lock用於查找和索引掃描,防止幻讀
查看鎖狀態
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t` trx id 10080 lock_mode X Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 0: len 8; hex 73757072656d756d; asc supremum;; Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 8000000a; asc ;; 1: len 6; hex 00000000274f; asc 'O;; 2: len 7; hex b60000019d0110; asc ;;
插入意圖鎖在執行插入行數據以前的設置的一種間隙鎖
這個鎖表示,在想要插入的時候,多個事務插入共同的索引間隙不須要相互等待,若果他們的行在間隙中位置不一樣
假設,47上有索引記錄鎖,獨立的事務想要插入5和6,每一個事務在獲得插入行上的排他鎖以前,使用插入意圖鎖鎖住47這個間隙。可是每一個事務不阻塞彼此
舉例:客戶端A建立一張表有2個索引數據(90和102),開始一個事務,放置一個排他索引記錄鎖在ID>100的數據,排他鎖也包括一個102數據以前的間隙鎖
mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB; mysql> INSERT INTO child (id) values (90),(102); mysql> START TRANSACTION; mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE; +-----+ | id | +-----+ | 102 | +-----+
客戶端B開始一個事務插入一條記錄到間隙中,這個事務在等待排他鎖的時候持有一個插入意圖鎖
mysql> START TRANSACTION; mysql> INSERT INTO child (id) VALUES (101);
show engine innodb status查看
RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child` trx id 8731 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 0: len 4; hex 80000066; asc f;; 1: len 6; hex 000000002215; asc " ;; 2: len 7; hex 9000000172011c; asc r ;;...
一個AUTO-INC鎖是一個特殊的表級鎖,當事務插入表並有自增列的時候持有。舉例,若是一個事務在插入數據,其餘事務須要等待本身的插入,第一個事務將獲得連續的主鍵值
客戶端A建立一個包含兩個索引記錄(90和102)的表,而後啓動一個事務,該事務對ID大於100的索引記錄放置獨佔鎖。獨佔鎖包括記錄102以前的間隙鎖:
mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);
mysql> START TRANSACTION; mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;