數據一致性,鎖與隔離等級

這篇文章主要針對的是關係型數據庫(MySQL以及InnoDB引擎)而去討論的數據一致性,鎖以及MVCC機制,和隔離等級等相關的話題。之前寫過一篇文章針對基礎概念(理解數據庫的事務,ACID,CAP和一致性),而本篇主要是通過MySQL和InnoDB的具體實現來更加細緻地闡釋這些話題。

本篇不涉及分佈式系統的一致性問題,而主要關注的是事務的一致性問題。

數據一致性問題

要理解數據的一致性,先要明白在哪些情況可能會導致數據不一致。只有當併發操作發生時,纔會導致數據不一致的情況,而這包含以下幾種情況:

不一致場景 Transaction A Transaction B
幻讀(跟insert有關) select xx from table where x>3; 假設選取了10條記錄
insert 1條記錄 x = 100;
select xx from table where x>3; 選出了11條記錄(同樣條件)
結果 同樣條件,第一次讀的時候是10條記錄,第二次讀的時候是11條記錄
不可重複讀(跟多次讀有關) read x=10
update x=12
read x=12
結果 讀同一個數據第一次是10,第二次是12
髒讀(讀取了髒數據) update x = 20
read x=20
rollback
結果 讀取了髒數據20,實際並沒有成功更新到20
修改丟失之提交覆蓋(跟同時修改有關) read x=10 read x=10
update x= x+1 (即x=11)
update x= x+2 (即x=12)
結果 邏輯上應該是x=13,但實際是x=12,Transaction A的修改丟失
修改丟失之回滾覆蓋(跟回滾有關) read x=10 read x=10
update x= x+1 (即x=11)
rollback
結果 邏輯上應該是x=11,但實際是x=10,Transaction A的修改丟失

解決數據不一致的問題,數據庫一般是通過鎖或者MVCC機制來實現的。接下來我們先來了解InnoDB中各種類型的鎖以及MVCC機制:

根據是否可共享劃分

  • 在InnoDB中,根據其是否可共享,而分爲S鎖和X鎖,如下:
描述
S鎖(共享鎖) 可以對同一個對象加S鎖,不會相互衝突
X鎖(排它鎖) 不可以對同一對象加X鎖,所以叫互斥鎖
  • Intention Lock,即IS和IX鎖,翻譯爲意圖鎖,實際是表明後續的意圖,比如加了IS鎖,表明後續有讀操作的意圖,後續要加S鎖。意圖鎖是表級鎖。
    示例:
    # 加IS鎖, 行級S鎖
    SELECT ... FOR SHARE
    
    # 加IX鎖,行級X鎖
    SELECT ... FOR UPDATE
    

鎖的互斥情況

S鎖 X鎖 IS鎖 IX鎖
S鎖 不衝突 衝突 不衝突 衝突
X鎖 衝突 衝突 衝突 衝突
IS鎖 不衝突 衝突 不衝突 不衝突
IX鎖 衝突 衝突 不衝突 不衝突

事實上意圖鎖不會和行級的S和X鎖產生衝突,只會和表級的S和X鎖產生衝突。因此,通常情況下,數據庫查詢,更新,插入,刪除 record都不會因爲意圖鎖而產生衝突(IS和IX互不衝突),但是在drop table或者更改表結構的這種操作時就會產生衝突。

  • Transaction中,如果程序要獲取S鎖來讀數據,那麼首先要獲取IS鎖或更強的IX鎖
  • Transaction中,如果程序要獲取X鎖來寫數據,那麼首先要獲取IX鎖

根據鎖的粒度以及鎖定的對象不同而劃分

  • 表級鎖,IS和IX就是表級鎖,這種鎖通常只是表明一種意圖,對於表的正常增刪改查幾乎沒有影響,但是當有表級操作的時候,就會有約束力了。
  • 行級鎖,在InnoDB中通過記錄鎖(record lock)來實現

用於鎖定特定記錄的索引,舉例而言下面這條語句會鎖定index上t=1的記錄, 這樣任何對t=1的update, insert和delete操作都無法執行(需要注意的是insert t=1的記錄也無法執行)

```
# 本條語句對t=1這條記錄加了X鎖
select * from tablename where t=1 for update; 
```
  • 間隙鎖(gap lock)

用於針對特定範圍的索引進行加鎖時,就需要用到間隙鎖,即針對索引記錄的間隙進行加鎖,從而防止特定的範圍內的插入操作,以防止幻讀現象的發生。舉個例子(注意:本例需在RR隔離等級):

select * from tablename where t between 10 and 20

這種情況下,在t index的10與20之間就加入了間隙鎖,這時是無法插入t的值在10和20之間的記錄的,例如無法插入t=16的記錄。

# 這種情況是否加間隙鎖要看t是否有index,且是unique類
# 型的index,如果是unique類型的index,則無需加gap 
# lock,否則就會對index 10之前的index加gap lock

select * from tablename where t=10

-- 間隙鎖可能加在兩個index的中間,也可能在第一個index的前邊,或者最後一個index的後邊
-- 間隙鎖之間不會互斥,它的作用僅僅在於防止插入新的數據,比如當某個間隙已經加了S鎖的情況下,仍然可以加X鎖,只要不是插入新數據,就不會衝突。
-- 間隙鎖主要在RR隔離等級下會起作用,其主要是爲了解決幻讀現象,而在RC隔離等級下,間隙鎖將只會用於外鍵約束檢查和Duplicate Key的檢查。

  • Next-key lock
    什麼是next-key lock? 以下引用自MySQL官網

A next-key lock is a combination of a record lock on the index record and a gap lock on the gap before the index record.

可以看出首先next-key lock是一種組合,組合了inex record lock和index gap lock。next-key lock是在RR隔離等級下爲解決幻讀而存在的一種鎖的組合。至於這個gap到底如何鎖定則需要視具體情況而定。
-- 當where查詢使用unique索引時,且查詢條件限定特定單一record,如下

# field a 已經加了unique索引
select * from testnk where a=5 for update;

這時,next-key只對index a=5這條記錄加鎖(IX和S鎖),而沒有間隙鎖。這時只要插入的記錄其a值不等於5,就可以插入

-- 當where查詢使用unique索引時,且查詢條件限定一個範圍時,如下:

# field a 已經加了unique索引
select * from testnk where a<8 for update;

這時,next-key lock會對a < 8這個特定範圍加鎖,當插入a的值爲6時,則無法插入,但是如果插入的a值爲9,則沒有問題。

-- 當where查詢使用普通索引時(非unique), 且查詢條件爲特定單一record

# field b 爲普通索引, int類型
# 假定已經有b=3,和b=8的記錄
select * from testnk where b=5

這時next-key lock會對[3, 8)這個範圍加鎖,即對b=5加record lock, 對[3, 5), (5, 8)範圍加gap lock, 即b=3, 4, 5, 6, 7都不能插入,b=8或其它值可以插入

-- 當where查詢使用普通索引時(非unique),且查詢條件限定一個範圍時,如下:

select * from testnk where b < 5 for update;

這時next-key lock會鎖定b < 5這個特定範圍

-- 當where查詢的field沒有索引的時候,不論查詢條件怎樣,都會lock整個表,這對於線上服務來說就是個災難,導致併發性能會嚴重下降,所以務必注意查詢條件的field是否加了索引。

  • 插入意圖鎖(Insert Intention Lock)

An insert intention lock is a type of gap lock set by INSERT operations prior to row insertion.

可以看出插入意圖鎖是一種間隙鎖,用於表明插入新數據的意圖。官方有個例子,假設我們數據庫中已有數據4和7, 現在要在兩個不同的事務中分別插入5和6,這時兩個事務都會獲取到4和7之間的插入意圖鎖,並在隨後分別獲得排他記錄鎖,分別鎖定兩條不同的行記錄,且都能插入成功。

同其它意圖一樣,在插入數據時,插入意圖鎖並不會互相沖突,一般都會正常獲取到該鎖,但是由於insert需要X鎖,這就需要看是否與其它鎖衝突。

  • 自增長鎖(AUTO-INC Locks)

An AUTO-INC lock is a special table-level lock taken by transactions inserting into tables with AUTO_INCREMENT columns. In the simplest case, if one transaction is inserting values into the table, any other transactions must wait to do their own inserts into that table, so that rows inserted by the first transaction receive consecutive primary key values.

自增長鎖主要用於帶有AUTO_INCREMENT的列,這個鎖主要用於保證自增長列的id的唯一性和增長性。MySQL 5.1中引入innodb_autoinc_lock_mode配置參數可以配置該鎖的不同模式,有三種模式:
-- innodb_autoinc_lock_mode=0,這時爲傳統模式,即會將整個表加鎖,同一時間只能有一個事務插入數據,且在插入語句完成後解鎖(而不是事務完成)
-- innodb_autoinc_lock_mode=1, 默認模式,當能夠預先知道插入的條數時,則爲"simple insert",這種情況下,只會在分配id的過程時有鎖定,保證分配id的唯一性和自增長性,其它時候(包括插入的時候都不需要鎖表);而當無法預先知道要插入的條數時,則爲"bulk insert",這時還是會按照傳統模式來鎖表,直到insert語句執行完畢。這種模式會在相當程度上改善併發性。
-- innodb_autoinc_lock_mode=2,混合模式,任何情況下都不會加AUTO_INCREAMENT表級鎖,併發性最好,但是並不安全,無法保證自增列的唯一性。

多版本併發控制MVCC(Multi-Version Concurrent Control)

如果完全使用鎖機制,則會導致讀寫操作無法併發執行,這在實際應用的時候會導致數據庫的併發性能受到很大影響,因此InnoDB引入了MVCC機制,使得讀寫操作可以併發執行,從而大大提高了數據庫性能。

其具體的實現是通過每行記錄的三個隱藏的字段(分別是隱藏id,事務id,回滾指針)以及undo log來實現的。具體可以參考MySQL數據庫事務各隔離級別加鎖情況--read committed && MVCC
簡而言之,當事務要針對一條記錄進行寫操作時,會先用X鎖鎖定該行,然後將該記錄copy到undo log當中,並通過回滾指針指向這條記錄,用於回滾恢復時使用。這時,其它事務可以通過讀取undo log中的備份數據來實現讀操作,從而實現讀寫併發。

MVCC只在InnoDB的RC和RR隔離等級下才能夠使用。因爲在Read Uncommitted這種隔離等級下,讀操作總是讀取記錄的最新數據,而不會去讀undo log中的備份數據;而如果是串行模式,讀操作不會發生衝突,因此也總是讀取的最新的記錄。

而在RC和RR模式下,MVCC也稍有不同。RC 總是讀取記錄的最新版本,如果該記錄被鎖住,則讀取該記錄最新的一次快照,而 RR 總是讀取事務開始時的那個快照,並在事務過程中始終讀取這個快照。

這種讀取快照的方式叫做快照讀(snapshot read), 也叫非阻塞讀(non-blocking read), 特別的,在RR模式下由於其始終讀取一個版本的快照,因此也叫一致性非阻塞讀(consistent non-blocking read)

而相對的,如果讀取的是實際的記錄而非快照,那麼這個叫做當前讀

隔離等級

而要解決數據不一致的問題,就需要通過設定不同的隔離等級來進行規避。標準的隔離等級表如下(注意,InnoDB在實現時會有不同,具體見註解):

隔離等級 原理描述 回滾覆蓋 解決髒讀 解決不可重複讀 提交覆蓋 解決幻讀
RU(Read Uncommitted) 讀的時候不加鎖,寫的瞬間加X鎖,直到事務結束釋放,因此我們可以讀取到未提交的數據,因此叫Read Uncommitted Yes No No Yes No
RC(Read Committed) 在讀的一瞬間加S鎖,讀完之後釋放S鎖;在寫的一瞬間加X鎖,直到事務結束,所以只能讀到committed之後的數據,所以叫read committed Yes Yes No No No
RR(Repeatable Read) 在讀的時候加持續S鎖,在寫的時候加持續X鎖,因此在Transaction過程中保證讀取的數據始終一致,也就避免不可重複讀問題 Yes Yes Yes No No
Serialization 最高隔離等級,通過表級鎖,使得每個數據庫操作都串行化,無法並行操作數據庫,這樣完全避免了數據不一致的問題,但由於無法並行操作數據庫,導致性能受到極大的限制,因此只能用於特定的場景 Yes Yes Yes Yes Yes
  • InnoDB在具體實現不同隔離等級的時候,會與標準稍有不同,需要加以區分。比如,在RR模式下,標準是要求讀數據的時候加持續S鎖,但是InnoDB具體實現的時候,要分不同的情況:

    # 當數據沒有被其它事務加鎖時,當前讀;當有被其它事務加鎖時,直接快照讀。可能出現提交覆蓋
    select * from tableA where x=5;  
    
    # 加持續S鎖,當前讀。可能出現提交覆蓋
    select * from tableA where x=5 lock in share mode; 
    
    # 加持續X鎖,當前讀。不會出現提交覆蓋
    select * from tableA where x=5 for update; 
    
  • 在標準的隔離等級中,RR隔離等級是不能解決幻讀問題的,但是InnoDB卻通過Next-key解決了幻讀問題

  • 標準的隔離等級中,是通過鎖來實現RC和RR等級的,而在InnoDB中,不僅僅有鎖機制,還有MVCC機制,是兩種機制結合在一起來實現RC和RR等級的。同時,由於引入了MVCC機制,使得其併發性更好。

  • 在RC模式下,當前讀加鎖時只加記錄鎖,不加間隙鎖,而在RR模式下會使用next-key鎖,因此會加間隙鎖,從而解決了幻讀現象。

MySQL實操

  • 查看和設置隔離等級
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.01 sec)

這裏要注意的是在 MySQL 中變量一般分爲兩類:用戶變量 和 系統變量,用戶變量的變量名格式爲 @variable,而系統變量的格式爲 @@variable,tx_isolation 是系統變量,所以變量名爲 @@tx_isolation。其中,系統變量又可以分爲 全局變量 和 會話變量,默認情況下使用 select @@variable 查詢出來的是會話變量的值,也可以寫作 select @@session.variable 或者 select @@local.variable,如果要查詢全局變量的值,則使用 select @@global.variable。 -- <<解決死鎖之路 - 學習事務與隔離級別>>

# 設置下一個事務的隔離等級
mysql> set transaction isolation level read committed;
Query OK, 0 rows affected (0.01 sec)

# 此時,去查當前session的隔離等級,仍然是repeatable-read
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)

# 要想修改session的隔離等級或者global的隔離等級,可以如下操作
mysql> set @@session.tx_isolation='READ-COMMITTED';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set, 1 warning (0.00 sec)

mysql> set @@global.tx_isolation='READ-COMMITTED';
Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-COMMITTED        |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
  • 查看鎖的狀態
# 查看錶鎖的情況
mysql> show status like 'table_locks%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Table_locks_immediate | 90    |
| Table_locks_waited    | 0     |
+-----------------------+-------+
2 rows in set (0.00 sec)

# 查看record lock的狀態,都是0表明很健康
mysql> show status like 'InnoDB_row_lock%';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0     |
| Innodb_row_lock_time          | 0     |
| Innodb_row_lock_time_avg      | 0     |
| Innodb_row_lock_time_max      | 0     |
| Innodb_row_lock_waits         | 0     |
+-------------------------------+-------+
5 rows in set (0.00 sec)

另外,我們可以通過show engine innodb status來查看鎖的狀態,但是在這之前我們需要先創建一個表,如下:

# 在任意db中創建該表,創建該表就會打開lock monitor
mysql> CREATE TABLE innodb_lock_monitor (a INT) ENGINE=INNODB;
Query OK, 0 rows affected (0.16 sec)

另外,我們可以通過查詢INFORMATION_SCHEMA中的innodb_locks, innodb_lock_waits和innodb_trx三張表來查詢正在等待的鎖的狀態, 關於這三張表的說明如下所示:

# 引用自《mysql 查看死鎖和去除死鎖》
[email protected] : information_schema 13:28:38> desc innodb_locks;
+————-+———————+——+—–+———+——-+
| Field       | Type                | Null | Key | Default | Extra |
+————-+———————+——+—–+———+——-+
| lock_id     | varchar(81)         | NO   |     |         |       |#鎖ID
| lock_trx_id | varchar(18)         | NO   |     |         |       |#擁有鎖的事務ID
| lock_mode   | varchar(32)         | NO   |     |         |       |#鎖模式
| lock_type   | varchar(32)         | NO   |     |         |       |#鎖類型
| lock_table  | varchar(1024)       | NO   |     |         |       |#被鎖的表
| lock_index  | varchar(1024)       | YES  |     | NULL    |       |#被鎖的索引
| lock_space  | bigint(21) unsigned | YES  |     | NULL    |       |#被鎖的表空間號
| lock_page   | bigint(21) unsigned | YES  |     | NULL    |       |#被鎖的頁號
| lock_rec    | bigint(21) unsigned | YES  |     | NULL    |       |#被鎖的記錄號
| lock_data   | varchar(8192)       | YES  |     | NULL    |       |#被鎖的數據
+————-+———————+——+—–+———+——-+
rows in set (0.00 sec)
   
[email protected] : information_schema 13:28:56> desc innodb_lock_waits;
+——————-+————-+——+—–+———+——-+
| Field             | Type        | Null | Key | Default | Extra |
+——————-+————-+——+—–+———+——-+
| requesting_trx_id | varchar(18) | NO   |     |         |       |#請求鎖的事務ID
| requested_lock_id | varchar(81) | NO   |     |         |       |#請求鎖的鎖ID
| blocking_trx_id   | varchar(18) | NO   |     |         |       |#當前擁有鎖的事務ID
| blocking_lock_id  | varchar(81) | NO   |     |         |       |#當前擁有鎖的鎖ID
+——————-+————-+——+—–+———+——-+
rows in set (0.00 sec)
   
[email protected] : information_schema 13:29:05> desc innodb_trx ;
+—————————-+———————+——+—–+———————+——-+
| Field                      | Type                | Null | Key | Default             | Extra |
+—————————-+———————+——+—–+———————+——-+
| trx_id                     | varchar(18)         | NO   |     |                     |       |#事務ID
| trx_state                  | varchar(13)         | NO   |     |                     |       |#事務狀態:
| trx_started                | datetime            | NO   |     | 0000-00-00 00:00:00 |       |#事務開始時間;
| trx_requested_lock_id      | varchar(81)         | YES  |     | NULL                |       |#innodb_locks.lock_id
| trx_wait_started           | datetime            | YES  |     | NULL                |       |#事務開始等待的時間
| trx_weight                 | bigint(21) unsigned | NO   |     | 0                   |       |#
| trx_mysql_thread_id        | bigint(21) unsigned | NO   |     | 0                   |       |#事務線程ID
| trx_query                  | varchar(1024)       | YES  |     | NULL                |       |#具體SQL語句
| trx_operation_state        | varchar(64)         | YES  |     | NULL                |       |#事務當前操作狀態
| trx_tables_in_use          | bigint(21) unsigned | NO   |     | 0                   |       |#事務中有多少個表被使用
| trx_tables_locked          | bigint(21) unsigned | NO   |     | 0                   |       |#事務擁有多少個鎖
| trx_lock_structs           | bigint(21) unsigned | NO   |     | 0                   |       |#
| trx_lock_memory_bytes      | bigint(21) unsigned | NO   |     | 0                   |       |#事務鎖住的內存大小(B)
| trx_rows_locked            | bigint(21) unsigned | NO   |     | 0                   |       |#事務鎖住的行數
| trx_rows_modified          | bigint(21) unsigned | NO   |     | 0                   |       |#事務更改的行數
| trx_concurrency_tickets    | bigint(21) unsigned | NO   |     | 0                   |       |#事務併發票數
| trx_isolation_level        | varchar(16)         | NO   |     |                     |       |#事務隔離級別
| trx_unique_checks          | int(1)              | NO   |     | 0                   |       |#是否唯一性檢查
| trx_foreign_key_checks     | int(1)              | NO   |     | 0                   |       |#是否外鍵檢查
| trx_last_foreign_key_error | varchar(256)        | YES  |     | NULL                |       |#最後的外鍵錯誤
| trx_adaptive_hash_latched  | int(1)              | NO   |     | 0                   |       |#
| trx_adaptive_hash_timeout  | bigint(21) unsigned | NO   |     | 0                   |       |#
+—————————-+———————+——+—–+———————+——-+
rows in set (0.01 sec)

正常沒有鎖等待的情況下,查詢這三張表是空的,如下:

mysql> select * from information_schema.innodb_locks;
Empty set, 1 warning (0.02 sec)

mysql> select * from information_schema.innodb_lock_waits;
Empty set, 1 warning (0.00 sec)

mysql> select * from information_schema.innodb_trx;
Empty set (0.00 sec)

下面我們做個試驗,讓兩個事務交互,形成鎖等待,如下:

# 事務1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

# 此條語句會獲得X鎖,且會獲得next-key鎖,鎖定b>5的範圍
mysql> select * from testnkb where b > 5 for update;
+---+
| b |
+---+
| 8 |
| 8 |
+---+
2 rows in set (0.01 sec)

然後事務2:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.01 sec)

# 此條語句會導致鎖等待,等待事務1的鎖釋放
mysql> update testnkb set b=10 where b=8;

此時,我們再去通過information_schema中的三個表去查詢

mysql> select * from information_schema.innodb_trx\G;
*************************** 1. row ***************************
                    trx_id: 7120  # transaction id
                 trx_state: LOCK WAIT
               trx_started: 2019-07-26 20:56:43
     trx_requested_lock_id: 7120:184:4:4  # 在等待的鎖的id
          trx_wait_started: 2019-07-26 20:56:43
                trx_weight: 2
       trx_mysql_thread_id: 4407
                 trx_query: update testnkb set b=10 where b=8 # 實際等待中的語句
       trx_operation_state: starting index read
         trx_tables_in_use: 1
         trx_tables_locked: 1
          trx_lock_structs: 2
     trx_lock_memory_bytes: 1136
           trx_rows_locked: 1
         trx_rows_modified: 0
   trx_concurrency_tickets: 0
       trx_isolation_level: REPEATABLE READ
         trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 0
          trx_is_read_only: 0
trx_autocommit_non_locking: 0
*************************** 2. row ***************************
                    trx_id: 7119
                 trx_state: RUNNING
               trx_started: 2019-07-26 20:55:45
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 3
       trx_mysql_thread_id: 4364
                 trx_query: select * from information_schema.innodb_trx
       trx_operation_state: NULL
         trx_tables_in_use: 0
         trx_tables_locked: 1
          trx_lock_structs: 3
     trx_lock_memory_bytes: 1136
           trx_rows_locked: 5
         trx_rows_modified: 0
   trx_concurrency_tickets: 0
       trx_isolation_level: REPEATABLE READ
         trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 0
          trx_is_read_only: 0
trx_autocommit_non_locking: 0
2 rows in set (0.00 sec)

ERROR:
No query specified

mysql> select * from information_schema.innodb_lock_waits\G;
*************************** 1. row ***************************
requesting_trx_id: 7120  # 正在等待的Transaction id
requested_lock_id: 7120:184:4:4
  blocking_trx_id: 7119  # 正在佔用鎖的Transaction id
 blocking_lock_id: 7119:184:4:4
1 row in set, 1 warning (0.00 sec)

ERROR:
No query specified

mysql> select * from information_schema.innodb_locks\G;
*************************** 1. row ***************************
    lock_id: 7120:184:4:4
lock_trx_id: 7120
  lock_mode: X  # X鎖
  lock_type: RECORD
 lock_table: `test`.`testnkb`
 lock_index: b
 lock_space: 184
  lock_page: 4
   lock_rec: 4
  lock_data: 8, 0x000000000206
*************************** 2. row ***************************
    lock_id: 7119:184:4:4
lock_trx_id: 7119
  lock_mode: X
  lock_type: RECORD
 lock_table: `test`.`testnkb`
 lock_index: b
 lock_space: 184
  lock_page: 4
   lock_rec: 4
  lock_data: 8, 0x000000000206
2 rows in set, 1 warning (0.00 sec)

ERROR:
No query specified

另外,我們可以通過show engine innodb status看到更多信息,如下:

# 截取Transaction部分
------------
TRANSACTIONS
------------
Trx id counter 7121
Purge done for trx's n:o < 7066 undo n:o < 0 state: running but idle
History list length 2
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 7120, ACTIVE 80 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 4407, OS thread handle 123145393938432, query id 22951 localhost root Searching rows for update
update testnkb set b=10 where b=8
------- TRX HAS BEEN WAITING 4 SEC FOR THIS LOCK TO BE GRANTED:
# 這裏表明Transaction 7120正在等待X鎖
RECORD LOCKS space id 184 page no 4 n bits 72 index b of table `test`.`testnkb` trx id 7120 lock_mode X waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 80000008; asc     ;;
 1: len 6; hex 000000000206; asc       ;;

------------------
---TRANSACTION 7119, ACTIVE 138 sec
# 3種鎖,猜測應該是IX鎖,record X鎖,以及gap鎖
3 lock struct(s), heap size 1136, 5 row lock(s)
# 被4364這個thread佔用鎖,通過show processlist也能看到4364這個線程在佔用
MySQL thread id 4364, OS thread handle 123145395331072, query id 22952 localhost root starting
show engine innodb status

我們也可以通過show processlist;來查看運行中的事務

mysql> show processlist;
+------+------+-----------+------+---------+------+----------+------------------+
| Id   | User | Host      | db   | Command | Time | State    | Info             |
+------+------+-----------+------+---------+------+----------+------------------+
| 4364 | root | localhost | test | Sleep   |    7 |          | NULL             |
| 4407 | root | localhost | test | Query   |    0 | starting | show processlist |
+------+------+-----------+------+---------+------+----------+------------------+
2 rows in set (0.00 sec)

正常情況下會在info中顯示SQL語句,但是由於我們在事務1中執行的select... for update;語句已經執行完畢,所以這裏顯示爲null。從此也可以分析出某個事務一直沒有提交,但是也沒有執行SQL語句,可能是在執行事務過程中出現問題導致一直無法提交。

  • 查看Auto-inc鎖的模式
mysql> show variables like 'innodb_autoinc_lock_mode';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_autoinc_lock_mode | 1     |
+--------------------------+-------+
1 row in set (0.00 sec)

References

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章