Mysql InnoDB的鎖定機制

1.InnoDB的鎖定機制

InnoDB存儲引擎支持行級鎖,支持事務處理,事務是有一組SQL語句組成的邏輯處理單元,他的ACID特性以下:mysql

  • 原子性(Atomicity): 事務具備原子不可分割的特性,要麼一塊兒執行,要麼都不執行。
  • 一致性(Consistency): 在事務開始和事務結束時,數據都保持一致狀態。
  • 隔離性(Isolation): 在事務開始和結束過程當中,事務保持着必定的隔離特性,保證事務不受外部併發數據操做的影響。
  • 持久性(Durability): 在事務完成後,數據將會被持久化到數據庫中。

併發事務能提升數據庫資源的利用率,提升了數據庫的事務吞吐量,但併發事務也存在一些問題,主要包括:sql

  • 更新丟失(Lost Update): 兩個事務更新同一條數據,但第二個事務中途失敗退出,致使兩個修改都失效了;由於此時數據庫沒有執行任何鎖操做,併發事務並無被隔離。(現代數據庫已經不存在這種問題)
  • 髒讀(Dirty Reads): 一個事務讀了某行數據,可是另外一個事務已經更新了這行數據,這是很是危險的,極可能致使全部的操做被回滾。
  • 不可重複讀: 一個事務對一行數據重複讀取兩次(屢次),但是獲得了不一樣的結果,在兩次讀取過程當中,有可能存在另外一個事務對數據進行了修改。
  • 幻讀:事務在操做過程當中進行兩次查詢,第二次查詢結果包含了第一次沒有出現的數據。出現幻讀的主要緣由是兩次查詢過程當中另外一個事務插入新的數據。

數據庫併發中的「更新丟失」一般應該是徹底避免的,但防止更新丟失數據,並不能單靠數據庫事務控制來解決,須要應用程序對要更新的數據加必要的鎖來解決,而以上出現的數據庫問題都必要由數據庫提供必定的事務隔離機制來解決。爲了不數據庫事務併發帶來的問題,在標準SQL規範中定義了4個事務的隔離級別,不一樣的隔離級別對事務處理不同。數據庫

數據庫隔離級別的比較
隔離級別 讀數據一致性 髒讀 不可重複讀 幻讀
未提交讀
(Read uncommitted)
最低級別,只能保證不讀取物理上損壞的數據
已提交讀
(Read committed)
語句級
可重複讀
(Repeatable read)
事務級
可序列化
(Serializable)
最高級別,事務級

InnoDB存儲引擎實現了4中行鎖,分別時共享鎖(S)、排他鎖(X)、意向共享鎖(IS)、意向排他鎖(IX)。session

  • 共享鎖:你們都能讀,可是不能改,只有其中一個獨佔共享鎖時候才能改;
  • 排它鎖:我要改,大家都不能改,也不能讀(但能夠MVCC快照讀)

理解意向鎖

意向鎖不會和行級的S和X鎖衝突,只會和表級的S和X鎖衝突
意向鎖是爲了不遍歷所有行鎖併發

考慮這個例子:
事務A鎖住了表中的一行,讓這一行只能讀,不能寫。
以後,事務B申請整個表的寫鎖。rest

若是事務B申請成功,那麼理論上它就能修改表中的任意一行,這與A持有的行鎖是衝突的。code

數據庫須要避免這種衝突,就是說要讓B的申請被阻塞,直到A釋放了行鎖。
數據庫要怎麼判斷這個衝突呢?
step1:判斷表是否已被其餘事務用表鎖鎖表
step2:判斷表中的每一行是否已被行鎖鎖住。
注意step2,這樣的判斷方法效率實在不高,由於須要遍歷整個表。索引

因而就有了意向鎖。
在乎向鎖存在的狀況下,事務A必須先申請表的意向共享鎖,成功後再申請一行的行鎖。
在乎向鎖存在的狀況下,上面的判斷能夠改爲
step1:不變
step2:發現表上有意向共享鎖,說明表中有些行被共享行鎖鎖住了,所以,事務B申請表的寫鎖會被阻塞。事務

1.1經過索引檢索數據,上共享鎖,行鎖(若是不經過索引,會使用表鎖)

1.1經過索引檢索數據,上共享鎖,行鎖
SessionA                                        SessionB
mysql> set autocommit=0;                        mysql> set autocommit=0;
Query OK, 0 rows affected (0.02 sec)            Query OK, 0 rows affected (0.02 sec)
mysql> select * from test;                      mysql> select * from test;
--------------------------------------------------------------------------------
+----+-------+-------+-------+                  +----+-------+-------+-------+                            
| id | name  | money | level |                  | id | name  | money | level |
+----+-------+-------+-------+                  +----+-------+-------+-------+
|  1 | tom   |   100 |     1 |                  |  1 | tom   |   100 |     1 |
|  2 | jack  |   200 |     2 |                  |  2 | jack  |   200 |     2 |
|  3 | lucas |   300 |     3 |                  |  3 | lucas |   300 |     3 |
+----+-------+-------+-------+                  +----+-------+-------+-------+
3 rows in set (0.00 sec)                        3 rows in set (0.00 sec)
--------------------------------------------------------------------------------
對主鍵索引上共享鎖,其餘事務也能獲取到共享鎖
mysql> select * from test where                 
id=1 lock in share mode;
+----+------+-------+-------+
| id | name | money | level |
+----+------+-------+-------+
|  1 | tom  |   100 |     1 |
+----+------+-------+-------+
1 row in set (0.01 sec)
--------------------------------------------------------------------------------
                                                事務B也能繼續加共享鎖
                                                mysql> select * from test where                 
                                                id=1 lock in share mode;
                                                +----+------+-------+-------+
                                                | id | name | money | level |
                                                +----+------+-------+-------+
                                                |  1 | tom  |   100 |     1 |
                                                +----+------+-------+-------+
                                                1 row in set (0.01 sec)
                                                但沒法更新,由於事務A也加了共享鎖
                                                mysql> update test set level=11 where id=1;
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
                                                
                                                MORE:
                                                沒法加排它鎖
                                                select *from test where id=1 for update;
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
                                                
                                                能夠更新未加鎖的,好比
                                                mysql> update test set level=11 where id=2;
                                                Query OK, 1 row affected (0.00 sec)
                                                Rows matched: 1  Changed: 1  Warnings: 0
--------------------------------------------------------------------------------
事務A也沒法更新,由於事務B加了共享鎖
mysql> update test set level=11 where id=1;
ERROR 1205 (HY000): Lock wait timeout excee
ded; try restarting transaction
--------------------------------------------------------------------------------
                                                任意一個釋放共享鎖,則獨佔共享鎖的事務能夠更新
                                                mysql> commit;
                                                Query OK, 0 rows affected (0.00 sec)
--------------------------------------------------------------------------------
事務B釋放鎖,事務A獨佔,能夠更新了
mysql> update test set level=11 where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

1.2經過索引檢索數據,上排他鎖,行鎖

1.2經過索引檢索數據,上排他鎖,行鎖
SessionA                                        SessionB
mysql> set autocommit=0;                        mysql> set autocommit=0;
Query OK, 0 rows affected (0.02 sec)            Query OK, 0 rows affected (0.02 sec)
mysql> select * from test;                      mysql> select * from test;
--------------------------------------------------------------------------------
+----+-------+-------+-------+                  +----+-------+-------+-------+                            
| id | name  | money | level |                  | id | name  | money | level |
+----+-------+-------+-------+                  +----+-------+-------+-------+
|  1 | tom   |   100 |     1 |                  |  1 | tom   |   100 |     1 |
|  2 | jack  |   200 |     2 |                  |  2 | jack  |   200 |     2 |
|  3 | lucas |   300 |     3 |                  |  3 | lucas |   300 |     3 |
+----+-------+-------+-------+                  +----+-------+-------+-------+
3 rows in set (0.00 sec)                        3 rows in set (0.00 sec)
--------------------------------------------------------------------------------
對主鍵索引上排他鎖
mysql> select *from test where
id=1 for update;
+----+------+-------+-------+
| id | name | money | level |
+----+------+-------+-------+
|  1 | tom  |   100 |     1 |
+----+------+-------+-------+
1 row in set (0.01 sec)
--------------------------------------------------------------------------------
                                                事務B則不能繼續上排它鎖,會發生等待
                                                mysql> select *from test where id=1 for update;
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
                                                
                                                MORE:
                                                也不能更新,由於更新也是上排它鎖
                                                mysql> update test set level=2 where id=1;
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
                                                
                                                也不能上共享鎖
                                                mysql> select * from test where level=1 lock in share mode;
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
--------------------------------------------------------------------------------
事務A能夠更新
mysql> update test set level=11 where id=1;
Query OK, 1 row affected (0.08 sec)
Rows matched: 1  Changed: 1  Warnings: 0
--------------------------------------------------------------------------------
釋放排它鎖
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
--------------------------------------------------------------------------------
                                                事務A釋放鎖,事務B就能夠加排它鎖了
                                                mysql> select * from test where id=1 for update;
                                                +----+------+-------+-------+
                                                | id | name | money | level |
                                                +----+------+-------+-------+
                                                |  1 | tom  |   100 |     1 |
                                                +----+------+-------+-------+
                                                1 row in set (0.00 sec)

1.3經過索引更新數據,也是上排他鎖,行鎖

對於update,insert,delete語句會自動加排它鎖ci

1.3經過索引更新數據,也是上排他鎖,行鎖
SessionA                                        SessionB
mysql> set autocommit=0;                        mysql> set autocommit=0;
Query OK, 0 rows affected (0.02 sec)            Query OK, 0 rows affected (0.02 sec)
mysql> select * from test;                      mysql> select * from test;
--------------------------------------------------------------------------------
+----+-------+-------+-------+                  +----+-------+-------+-------+                            
| id | name  | money | level |                  | id | name  | money | level |
+----+-------+-------+-------+                  +----+-------+-------+-------+
|  1 | tom   |   100 |     1 |                  |  1 | tom   |   100 |     1 |
|  2 | jack  |   200 |     2 |                  |  2 | jack  |   200 |     2 |
|  3 | lucas |   300 |     3 |                  |  3 | lucas |   300 |     3 |
+----+-------+-------+-------+                  +----+-------+-------+-------+
3 rows in set (0.00 sec)                        3 rows in set (0.00 sec)
--------------------------------------------------------------------------------
更新id=1的行,就給該行上了排它鎖,其餘事務
沒法更新該行
mysql> update test set level=11 where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
--------------------------------------------------------------------------------
                                                事務B則不能更新id=1的行,會發生等待
                                                mysql> update test set level=21 where id=1;
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
                                                
                                                MORE:
                                                也不能上排它鎖
                                                mysql> select *from test where id=1 for update;
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
                                                
                                                也不能上共享鎖
                                                mysql> select * from test where level=1 lock in share mode;
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
--------------------------------------------------------------------------------
釋放排它鎖
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
--------------------------------------------------------------------------------
                                                事務A釋放鎖,事務B就能夠加排它鎖了
                                                mysql> select * from test where id=1 for update;
                                                +----+------+-------+-------+
                                                | id | name | money | level |
                                                +----+------+-------+-------+
                                                |  1 | tom  |   100 |     11|
                                                +----+------+-------+-------+
                                                1 row in set (0.00 sec)

2髒讀、不可重複讀與幻讀

2.1髒讀

//2.1髒讀
SessionA                                        SessionB
mysql> set autocommit=0;                        mysql> set autocommit=0;
Query OK, 0 rows affected (0.02 sec)            Query OK, 0 rows affected (0.02 sec)
set session transaction isolation               set session transaction isolation level read uncommitted;
level read uncommitted;                         Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)            
mysql> select * from test;                      mysql> select * from test;
--------------------------------------------------------------------------------
+----+-------+-------+-------+                  +----+-------+-------+-------+                            
| id | name  | money | level |                  | id | name  | money | level |
+----+-------+-------+-------+                  +----+-------+-------+-------+
|  1 | tom   |   100 |     1 |                  |  1 | tom   |   100 |     1 |
|  2 | jack  |   200 |     2 |                  |  2 | jack  |   200 |     2 |
|  3 | lucas |   300 |     3 |                  |  3 | lucas |   300 |     3 |
+----+-------+-------+-------+                  +----+-------+-------+-------+
3 rows in set (0.00 sec)                        3 rows in set (0.00 sec)
--------------------------------------------------------------------------------
                                                mysql> update test set level=100 where id=1;
                                                Query OK, 1 row affected (0.00 sec)
                                                Rows matched: 1  Changed: 1  Warnings: 0
--------------------------------------------------------------------------------
//髒讀
mysql> select *from test where id=1;
+----+------+-------+-------+
| id | name | money | level |
+----+------+-------+-------+
|  1 | tom  |   100 |   100 |
+----+------+-------+-------+
1 row in set (0.00 sec)
--------------------------------------------------------------------------------
                                                rollback;
                                                Query OK, 0 rows affected (0.01 sec)
                                                
                                                mysql> select *from test where id=1;
                                                +----+------+-------+-------+
                                                | id | name | money | level |
                                                +----+------+-------+-------+
                                                |  1 | tom  |   100 |     1 |
                                                +----+------+-------+-------+
                                                1 row in set (0.00 sec)

2.2不可重複讀

//2.2不可重複讀
SessionA                                        SessionB
mysql> set autocommit=0;                        mysql> set autocommit=0;
Query OK, 0 rows affected (0.02 sec)            Query OK, 0 rows affected (0.02 sec)
set session transaction isolation               set session transaction isolation level read uncommitted;
level read uncommitted;                         Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)            
mysql> select * from test;                      mysql> select * from test;
--------------------------------------------------------------------------------
+----+-------+-------+-------+                  +----+-------+-------+-------+                            
| id | name  | money | level |                  | id | name  | money | level |
+----+-------+-------+-------+                  +----+-------+-------+-------+
|  1 | tom   |   100 |     1 |                  |  1 | tom   |   100 |     1 |
|  2 | jack  |   200 |     2 |                  |  2 | jack  |   200 |     2 |
|  3 | lucas |   300 |     3 |                  |  3 | lucas |   300 |     3 |
+----+-------+-------+-------+                  +----+-------+-------+-------+
3 rows in set (0.00 sec)                        3 rows in set (0.00 sec)
--------------------------------------------------------------------------------
                                                mysql> update test set level=100 where id=1;
                                                Query OK, 1 row affected (0.00 sec)
                                                Rows matched: 1  Changed: 1  Warnings: 0
--------------------------------------------------------------------------------
mysql> select *from test where id=1;
+----+------+-------+-------+
| id | name | money | level |
+----+------+-------+-------+
|  1 | tom  |   100 |   100 |
+----+------+-------+-------+
1 row in set (0.00 sec)
--------------------------------------------------------------------------------
                                                mysql> update test set level=1000 where id=1;
                                                Query OK, 1 row affected (0.00 sec)
                                                Rows matched: 1  Changed: 1  Warnings: 0
--------------------------------------------------------------------------------
//不可重複讀
//讀三次,第一次是level是1,第二次是100,第三次是1000
mysql> select *from test where id=1;
+----+------+-------+-------+
| id | name | money | level |
+----+------+-------+-------+
|  1 | tom  |   100 |   1000|
+----+------+-------+-------+
1 row in set (0.00 sec)

2.3幻讀

//2.3幻讀
SessionA                                        SessionB
mysql> set autocommit=0;                        mysql> set autocommit=0;
Query OK, 0 rows affected (0.02 sec)            Query OK, 0 rows affected (0.02 sec)
set session transaction isolation               set session transaction isolation level read uncommitted;
level read uncommitted;                         Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)            
mysql> select * from test;                      mysql> select * from test;
--------------------------------------------------------------------------------
+----+-------+-------+-------+                  +----+-------+-------+-------+                            
| id | name  | money | level |                  | id | name  | money | level |
+----+-------+-------+-------+                  +----+-------+-------+-------+
|  1 | tom   |   100 |     1 |                  |  1 | tom   |   100 |     1 |
|  2 | jack  |   200 |     2 |                  |  2 | jack  |   200 |     2 |
|  3 | lucas |   300 |     3 |                  |  3 | lucas |   300 |     3 |
+----+-------+-------+-------+                  +----+-------+-------+-------+
3 rows in set (0.00 sec)                        3 rows in set (0.00 sec)
--------------------------------------------------------------------------------
                                                mysql> update test set level=100 where id=1;
                                                Query OK, 1 row affected (0.00 sec)
                                                Rows matched: 1  Changed: 1  Warnings: 0
--------------------------------------------------------------------------------
mysql> select *from test where id=1;
+----+------+-------+-------+
| id | name | money | level |
+----+------+-------+-------+
|  1 | tom  |   100 |   100 |
+----+------+-------+-------+
1 row in set (0.00 sec)
--------------------------------------------------------------------------------
                                                mysql> insert into test (name, money,level) VALUES                                                          ('tim',250,4);
                                                Query OK, 1 row affected (0.01 sec)
--------------------------------------------------------------------------------
//幻讀
//讀兩次,第二次多了tim的數據
//若是是rr級別,須要使用當前讀select * from test lock in share mode;不然由於MVCC的緣故,是讀不到tim的
mysql> select * from test;
+----+-------+-------+-------+
| id | name  | money | level |
+----+-------+-------+-------+
|  1 | tom   |   100 |     1 |
|  2 | jack  |   200 |     2 |
|  3 | lucas |   300 |     3 |
|  4 | tim   |   250 |     4 |
+----+-------+-------+-------+
4 row in set (0.00 sec)

3 間隙鎖(Net-Key鎖)

MVCC使RR級別下,事務當前讀,來避免了讀狀況下的幻讀問題,但若是寫更新時候呢?在範圍更新的同時,往範圍內插入新數據,怎麼辦?
因而就有了間隙鎖,在更新某個區間數據時,將會鎖定這個區間的全部記錄。例如update XXX where id between 1 and 100, 就會鎖住id從1到100之間的全部的記錄。值得注意的是,在這個區間中假設某條記錄並不存在,該條記錄也會被鎖住,這時,若是另外一個事務往這個區間添加數據,就必須等待上一個事務釋放鎖資源。
使用間隙鎖有兩個目的,一是防止幻讀;二是知足其恢復和賦值的需求。

3.1範圍間隙鎖,顯式左開右閉區間

//間隙鎖(Net-Key鎖) 範圍間隙鎖,左開右閉區間
SessionA                                        SessionB
mysql> set autocommit=0;                        mysql> set autocommit=0;
Query OK, 0 rows affected (0.02 sec)            Query OK, 0 rows affected (0.02 sec)
          
mysql> select * from test;                      mysql> select * from test;
--------------------------------------------------------------------------------
+----+-------+-------+-------+                  +----+-------+-------+-------+                            
| id | name  | money | level |                  | id | name  | money | level |
+----+-------+-------+-------+                  +----+-------+-------+-------+
|  1 | tom   |   100 |     1 |                  |  1 | tom   |   100 |     1 |
|  2 | jack  |   200 |     2 |                  |  2 | jack  |   200 |     2 |
|  3 | lucas |   300 |     3 |                  |  3 | lucas |   300 |     3 |
+----+-------+-------+-------+                  +----+-------+-------+-------+
3 rows in set (0.00 sec)                        3 rows in set (0.00 sec)
--------------------------------------------------------------------------------
mysql> update test set level=0
where money between 0 and 200;
Query OK, 2 rows affected (0.02 sec)
Rows matched: 2  Changed: 2  Warnings: 0
理論上應該鎖定[0,300)這個區間
--------------------------------------------------------------------------------
                                                插入money=0等待
                                                mysql> insert into test (name, money,level) VALUES ('tim',0,0);
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
                                                
                                                插入money=90等待
                                                mysql> insert into test (name, money,level) VALUES ('tim',90,0);
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
                                                
                                                插入money=100等待
                                                mysql> insert into test (name, money,level) VALUES ('tim',100,0);
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
                                                
                                                插入money=299等待
                                                mysql> insert into test (name, money,level) VALUES ('tim',299,0);
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
                                                
                                                插入money=300 ok
                                                mysql> insert into test (name, money,level) VALUES ('tim',300,0);
                                                Query OK, 1 row affected (0.00 sec)

3.2單個間隙鎖 隱式區間

上小節是指定update某個區間,那若是說是隻update一個值呢?還會有間隙鎖麼?

//間隙鎖(Net-Key鎖) 單個間隙鎖,左開右閉區間
SessionA                                        SessionB
mysql> set autocommit=0;                        mysql> set autocommit=0;
Query OK, 0 rows affected (0.02 sec)            Query OK, 0 rows affected (0.02 sec)
          
mysql> select * from test;                      mysql> select * from test;
--------------------------------------------------------------------------------
+----+-------+-------+-------+                  +----+-------+-------+-------+                            
| id | name  | money | level |                  | id | name  | money | level |
+----+-------+-------+-------+                  +----+-------+-------+-------+
|  1 | tom   |   100 |     1 |                  |  1 | tom   |   100 |     1 |
|  2 | jack  |   200 |     2 |                  |  2 | jack  |   200 |     2 |
|  3 | lucas |   300 |     3 |                  |  3 | lucas |   300 |     3 |
+----+-------+-------+-------+                  +----+-------+-------+-------+
3 rows in set (0.00 sec)                        3 rows in set (0.00 sec)
--------------------------------------------------------------------------------
mysql> update test set level=0
where money = 200;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
理論上應該鎖定[0,300)這個區間
--------------------------------------------------------------------------------
                                                插入money=0 ok
                                                mysql> insert into test (name, money,level) VALUES ('tim',0,0);
                                                Query OK, 1 row affected (0.00 sec)
                                                
                                                插入money=90 ok
                                                mysql> insert into test (name, money,level) VALUES ('tim',90,0);
                                                Query OK, 1 row affected (0.00 sec)
                                                
                                                插入money=100等待
                                                mysql> insert into test (name, money,level) VALUES ('tim',100,0);
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
                                                
                                                插入money=150等待
                                                mysql> insert into test (name, money,level) VALUES ('tim',150,0);
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
                                                
                                                插入money=200等待
                                                mysql> insert into test (name, money,level) VALUES ('tim',200,0);
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
                                                
                                                插入money=240等待
                                                mysql> insert into test (name, money,level) VALUES ('tim',240,0);
                                                ERROR 1205 (HY000): Lock wait timeout exceeded;
                                                try restarting transaction
                                                
                                                插入money=300 ok
                                                mysql> insert into test (name, money,level) VALUES ('tim',300,0);
                                                Query OK, 1 row affected (0.00 sec)

當不指定區間時,隱式的區間爲索引B+數先後兩個節點的值所肯定的區間,也是左開右閉,對於上述例子,就是[0,300)這個區間。

相關文章
相關標籤/搜索