| 隔離級別 | 讀數(shù)據(jù)一致性 | 臟讀 | 不可重復讀 | 幻讀 |
|---|---|---|---|---|
| 未提交讀 (Read uncommitted) |
最低級別,只能保證不讀取物理上損壞的數(shù)據(jù) | 是 | 是 | 是 |
| 已提交讀 (Read committed) |
語句級 | 否 | 是 | 是 |
| 可重復讀 (Repeatable read) |
事務(wù)級 | 否 | 否 | 是 |
| 可序列化 (Serializable) |
最高級別,事務(wù)級 | 否 | 否 | 否 |
InnoDB存儲引擎實現(xiàn)了4中行鎖,分別時共享鎖(S)、排他鎖(X)、意向共享鎖(IS)、意向排他鎖(IX)。
意向鎖不會和行級的S和X鎖沖突,只會和表級的S和X鎖沖突
意向鎖是為了避免遍歷全部行鎖
考慮這個例子:
事務(wù)A鎖住了表中的一行,讓這一行只能讀,不能寫。
之后,事務(wù)B申請整個表的寫鎖。
如果事務(wù)B申請成功,那么理論上它就能修改表中的任意一行,這與A持有的行鎖是沖突的。
數(shù)據(jù)庫需要避免這種沖突,就是說要讓B的申請被阻塞,直到A釋放了行鎖。
數(shù)據(jù)庫要怎么判斷這個沖突呢?
step1:判斷表是否已被其他事務(wù)用表鎖鎖表
step2:判斷表中的每一行是否已被行鎖鎖住。
注意step2,這樣的判斷方法效率實在不高,因為需要遍歷整個表。
于是就有了意向鎖。
在意向鎖存在的情況下,事務(wù)A必須先申請表的意向共享鎖,成功后再申請一行的行鎖。
在意向鎖存在的情況下,上面的判斷可以改成
step1:不變
step2:發(fā)現(xiàn)表上有意向共享鎖,說明表中有些行被共享行鎖鎖住了,因此,事務(wù)B申請表的寫鎖會被阻塞。
1.1通過索引檢索數(shù)據(jù),上共享鎖,行鎖(如果不通過索引,會使用表鎖)
1.1通過索引檢索數(shù)據(jù),上共享鎖,行鎖
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)
--------------------------------------------------------------------------------
對主鍵索引上共享鎖,其他事務(wù)也能獲取到共享鎖
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)
--------------------------------------------------------------------------------
事務(wù)B也能繼續(xù)加共享鎖
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)
但無法更新,因為事務(wù)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
--------------------------------------------------------------------------------
事務(wù)A也無法更新,因為事務(wù)B加了共享鎖
mysql> update test set level=11 where id=1;
ERROR 1205 (HY000): Lock wait timeout excee
ded; try restarting transaction
--------------------------------------------------------------------------------
任意一個釋放共享鎖,則獨占共享鎖的事務(wù)可以更新
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
--------------------------------------------------------------------------------
事務(wù)B釋放鎖,事務(wù)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通過索引檢索數(shù)據(jù),上排他鎖,行鎖
1.2通過索引檢索數(shù)據(jù),上排他鎖,行鎖
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)
--------------------------------------------------------------------------------
對主鍵索引上排他鎖,其他事務(wù)也能獲取到共享鎖
mysql> select *from test where
id=1 for update;
+----+------+-------+-------+
| id | name | money | level |
+----+------+-------+-------+
| 1 | tom | 100 | 1 |
+----+------+-------+-------+
1 row in set (0.01 sec)
--------------------------------------------------------------------------------
事務(wù)B則不能繼續(xù)上排它鎖,會發(fā)生等待
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
--------------------------------------------------------------------------------
事務(wù)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)
--------------------------------------------------------------------------------
事務(wù)A釋放鎖,事務(wù)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通過索引更新數(shù)據(jù),也是上排他鎖,行鎖
對于 update,insert,delete 語句會自動加排它鎖
1.3通過索引更新數(shù)據(jù),也是上排他鎖,行鎖
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的行,就給該行上了排它鎖,其他事務(wù)
無法更新該行
mysql> update test set level=11 where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
--------------------------------------------------------------------------------
事務(wù)B則不能更新id=1的行,會發(fā)生等待
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)
--------------------------------------------------------------------------------
事務(wù)A釋放鎖,事務(wù)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.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的數(shù)據(jù)
//如果是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)
MVCC使RR級別下,事務(wù)當前讀,來避免了讀情況下的幻讀問題,但如果寫更新時候呢?在范圍更新的同時,往范圍內(nèi)插入新數(shù)據(jù),怎么辦?
于是就有了間隙鎖,在更新某個區(qū)間數(shù)據(jù)時,將會鎖定這個區(qū)間的所有記錄。例如update XXX where id between 1 and 100, 就會鎖住id從1到100之間的所有的記錄。值得注意的是,在這個區(qū)間中假設(shè)某條記錄并不存在,該條記錄也會被鎖住,這時,如果另一個事務(wù)往這個區(qū)間添加數(shù)據(jù),就必須等待上一個事務(wù)釋放鎖資源。
使用間隙鎖有兩個目的,一是防止幻讀;二是滿足其恢復和賦值的需求。
3.1范圍間隙鎖,顯式左開右閉區(qū)間
//間隙鎖(Net-Key鎖) 范圍間隙鎖,左開右閉區(qū)間
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
理論上應(yīng)該鎖定[0,300)這個區(qū)間
--------------------------------------------------------------------------------
插入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單個間隙鎖 隱式區(qū)間
上小節(jié)是指定update某個區(qū)間,那如果說是只update一個值呢?還會有間隙鎖么?
//間隙鎖(Net-Key鎖) 單個間隙鎖,左開右閉區(qū)間
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
理論上應(yīng)該鎖定[0,300)這個區(qū)間
--------------------------------------------------------------------------------
插入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)
當不指定區(qū)間時,隱式的區(qū)間為索引B+數(shù)前后兩個節(jié)點的值所確定的區(qū)間,也是左開右閉,對于上述例子,就是[0,300)這個區(qū)間。
到此這篇關(guān)于Mysql InnoDB鎖定機制的文章就介紹到這了,更多相關(guān)Mysql InnoDB鎖定機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!