InnoDB的鎖機制:mysql
數據庫使用所是爲了支持更好的併發,提供數據的完整性和一致性。InnoDB是一個支持鎖的存儲引擎,鎖的類型有:共享鎖(S)、排它鎖(X)、意向共享鎖(IS)、意向排它鎖(IX)。爲了支持更好的併發,InnoDB提供了非鎖定讀:不須要等待訪問行上的鎖釋放,讀取行的一個快照。該方法是經過InnoDB的一個特寫:MVCC實現的。算法
InnoDB的鎖分類:sql
Record Lock:行鎖:單個行記錄上的行鎖數據庫
Gap Lock:間隙鎖,鎖定一個範圍,但不包括記錄自己session
Next-Key Lock:Gap+Record Lock,鎖定一個範圍,而且鎖定記錄自己併發
無索引+RC/RRide
當對無索引的字段進行更新時(RR級別),經過鎖主鍵的方式,來鎖住全部記錄,RC級別不會鎖全部記錄。spa
構建表及初始化數據:rest
mysql -uroot -p USE test; DROP TABLE IF EXISTS t_none; CREATE TABLE `t_none` ( `id` int(11) NOT NULL, `mem_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB; INSERT INTO t_none VALUES(1,1),(3,3),(5,5),(9,9),(11,11);
REPEATABLE-READ(RR)默認級別索引 |
|
Session A |
Session B |
root@localhost[zjkj]:10:53:18>prompt A>> PROMPT set to 'A>>' A>>select @@session.tx_isolation; |
root@localhost[(none)]:11:02:58>prompt B>> PROMPT set to 'B>>' B>>select @@session.tx_isolation; |
A>>begin; Query OK, 0 rows affected (0.00 sec) |
B>>begin; Query OK, 0 rows affected (0.00 sec) |
A>>select * from t_none; +----+--------+ | id | mem_id | +----+--------+ | 1 | 1 | | 3 | 3 | | 5 | 5 | | 9 | 9 | | 11 | 11 | +----+--------+ 5 rows in set (0.00 sec) |
B>>select * from t_none; +----+--------+ | id | mem_id | +----+--------+ | 1 | 1 | | 3 | 3 | | 5 | 5 | | 9 | 9 | | 11 | 11 | +----+--------+ 5 rows in set (0.00 sec) |
A>> select * from t_none where mem_id=3 for update; +----+--------+ | id | mem_id | +----+--------+ | 3 | 3 | +----+--------+ 1 row in set (0.01 sec) |
|
B>>insert into t_none values(2,2); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction B>>delete from t_none where id=9; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction |
|
show engin inondb status部分輸出: ------------ TRANSACTIONS ------------ Trx id counter 10661 Purge done for trx's n:o < 10659 undo n:o < 0 state: running but idle History list length 351 Total number of lock structs in row lock hash table 2 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 10588, not started MySQL thread id 4, OS thread handle 0x7f6f5085c700, query id 339 localhost root init show engine innodb status ---TRANSACTION 10660, ACTIVE 17 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s) MySQL thread id 11, OS thread handle 0x7f6f508de700, query id 338 localhost root update insert into t_none values(2,2) ------- TRX HAS BEEN WAITING 17 SEC FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 68 page no 3 n bits 72 index `PRIMARY` of table `test`.`t_none` trx id 10660 lock_mode X locks gap before rec insert intention waiting |
|
結論:經過上面很容易的看到,沒有經過索引for update時,當進行增刪改都會鎖住,MySQL內部會經過基於鎖默認主鍵方式,對全部記錄加X鎖。 下面是RC級別的實驗 |
|
Read Committed級別(RC) |
|
Session A |
Session B |
A>>set @@session.tx_isolation="read-committed"; Query OK, 0 rows affected (0.00 sec) |
B>>set @@session.tx_isolation="read-committed"; Query OK, 0 rows affected (0.00 sec) |
A>>select @@session.tx_isolation; +------------------------+ | @@session.tx_isolation | +------------------------+ | READ-COMMITTED | +------------------------+ 1 row in set (0.00 sec) |
B>>select @@session.tx_isolation; +------------------------+ | @@session.tx_isolation | +------------------------+ | READ-COMMITTED | +------------------------+ 1 row in set (0.01 sec) |
A>>begin; Query OK, 0 rows affected (0.00 sec) |
B>>begin; Query OK, 0 rows affected (0.00 sec) |
A>>select * from t_none where mem_id=3 for update; +----+--------+ | id | mem_id | +----+--------+ | 3 | 3 | +----+--------+ 1 row in set (0.01 sec) |
|
B>>insert into t_none values(2,2); Query OK, 1 row affected (0.01 sec) |
|
B>>select * from t_none; +----+--------+ | id | mem_id | +----+--------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | | 5 | 5 | | 9 | 9 | | 11 | 11 | +----+--------+ 6 rows in set (0.00 sec |
|
A>>rollback; Query OK, 0 rows affected (0.00 sec) |
B>>rollback; Query OK, 0 rows affected (0.00 sec) |
結論:在RC級別下,事務B是能夠進行增刪改(除被鎖定的記錄自己) |
非惟一索引+RR/RC
在RR級別下,InnoDB對於非惟一索引會加Gap Lock(也即鎖定一個區間),而在RC級別下無。
構造初始化表及數據:
mysql -uroot -p USE test; DROP TABLE IF EXISTS t_idx; CREATE TABLE `t_idx` ( `id` int(11) NOT NULL, `mem_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_mem_id` (`mem_id`) ) ENGINE=InnoDB; INSERT INTO t_idx VALUES(1,1),(3,3),(5,5),(9,9),(11,11);
REPEATABLE-READ(RR)默認級別(RR模式) | |
Session A |
Session B |
root@localhost[(none)]:06:01:59>use test; root@localhost[zjkj]:10:53:18>prompt A>> PROMPT set to 'A>>' |
root@localhost[(none)]:06:01:59>use test; root@localhost[(none)]:11:02:58>prompt B>> PROMPT set to 'B>>' |
A>>select @@session.tx_isolation; +------------------------+ | @@session.tx_isolation | +------------------------+ | REPEATABLE-READ | +------------------------+ 1 row in set (0.00 sec) |
B>>select @@session.tx_isolation; +------------------------+ | @@session.tx_isolation | +------------------------+ | REPEATABLE-READ | +------------------------+ 1 row in set (0.02 sec) |
A>>begin; Query OK, 0 rows affected (0.00 sec) |
B>>begin; Query OK, 0 rows affected (0.00 sec) |
A>>select * from t_idx; +----+--------+ | id | mem_id | +----+--------+ | 1 | 1 | | 3 | 3 | | 5 | 5 | | 9 | 9 | | 11 | 11 | +----+--------+ 5 rows in set (0.04 sec) |
B>>select * from t_idx; +----+--------+ | id | mem_id | +----+--------+ | 1 | 1 | | 3 | 3 | | 5 | 5 | | 9 | 9 | | 11 | 11 | +----+--------+ 5 rows in set (0.00 sec) |
A>>select * from t_idx where mem_id=3 for update; +----+--------+ | id | mem_id | +----+--------+ | 3 | 3 | +----+--------+ 1 row in set (0.05 sec) |
|
B>>insert into t_idx values(2,2); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction #問題?這裏爲何會出現阻塞呢? B>>insert into t_idx values(4,4); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction #問題?這裏爲何會出現阻塞呢? B>>insert into t_idx values(3,3); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction B>>insert into t_idx values(5,5); ERROR 1062 (23000): Duplicate entry '5' for key 'PRIMARY' B>>insert into t_idx values(1,1); ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY' #######下面插入所有能夠###### B>>insert into t_idx values(6,6); Query OK, 1 row affected (0.00 sec) B>>insert into t_idx values(7,7); B>>insert into t_idx values(8,8); Query OK, 1 row affected (0.01 sec) B>>insert into t_idx values(12,12); Query OK, 1 row affected (0.00 sec) |
|
B>>select * from t_idx; +----+--------+ | id | mem_id | +----+--------+ | 1 | 1 | | 3 | 3 | | 5 | 5 | | 6 | 6 | | 7 | 7 | | 8 | 8 | | 9 | 9 | | 11 | 11 | | 12 | 12 | +----+--------+ 9 rows in set (0.00 sec) |
|
show engine inondb status部分輸出: ------------ TRANSACTIONS ------------ Trx id counter 11044 Purge done for trx's n:o < 11041 undo n:o < 0 state: running but idle History list length 372 Total number of lock structs in row lock hash table 5 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 0, not started MySQL thread id 3, OS thread handle 0x7fd0430df700, query id 47 localhost root init show engine innodb status ---TRANSACTION 11039, ACTIVE 228 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1248, 3 row lock(s), undo log entries 4 MySQL thread id 1, OS thread handle 0x7fd064099700, query id 45 localhost root update insert into t_idx values(4,4) Trx read view will not see trx with id >= 11040, sees < 11038 ------- TRX HAS BEEN WAITING 22 SEC FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 70 page no 4 n bits 80 index `idx_mem_id` of table `test`.`t_idx` trx id 11039 lock_mode X locks gap before rec insert intention waitin |
|
結論:經過上面能夠看到,經過非惟一索引字段進行更新時,在進行增刪改時,有的記錄會出現阻塞,爲何會出現阻塞呢?其實就是用到了MySQL的間隙鎖。那MySQL這裏爲何要用間隙鎖呢?目的主要是防止幻讀。 那爲何有的記錄能夠插入有的不能夠,由於InnoDB對於行的查詢時採用了Next-Key Lock的算法,鎖定的是一個範圍(GAP)以下:(∞,1],(1,3],(3,5],(5,9],(9,11],(11, ∞)。InnoDB對輔助索引下一個鍵值也要加上Gap Lock,例如上面進行插入二、4、1、3、5時,就能夠看出,其實鎖住的區間是(1,5)。 | |
Read Committed級別(RC) | |
Session A |
Session B |
A>>rollback; Query OK, 0 rows affected (0.00 sec) |
B>>rollback; Query OK, 0 rows affected (0.00 sec) |
A>>set @@session.tx_isolation="read-committed"; Query OK, 0 rows affected (0.00 sec) |
B>>set @@session.tx_isolation="read-committed"; Query OK, 0 rows affected (0.00 sec) |
A>>select @@session.tx_isolation; +------------------------+ | @@session.tx_isolation | +------------------------+ | READ-COMMITTED | +------------------------+ 1 row in set (0.00 sec) |
B>>select @@session.tx_isolation; +------------------------+ | @@session.tx_isolation | +------------------------+ | READ-COMMITTED | +------------------------+ 1 row in set (0.01 sec) |
A>>begin; Query OK, 0 rows affected (0.00 sec) |
B>>begin; Query OK, 0 rows affected (0.00 sec) |
A>>select * from t_idx where mem_id=3 for update; +----+--------+ | id | mem_id | +----+--------+ | 1 | 3 | | 3 | 3 | +----+--------+ 2 rows in set (0.00 sec) |
|
B>>insert into t_idx values(1,1); ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY' B>>insert into t_idx values(2,2); Query OK, 1 row affected (0.00 sec) B>>insert into t_idx values(3,3); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction B>>insert into t_idx values(4,4); Query OK, 1 row affected (0.01 sec) |
|
結論:在RC級別下,事務B是能夠進行增刪改(除被鎖定的記錄自己),沒有出現間隙鎖的現象。 |
惟一索引+RR/RC
構造初始化表及數據:
mysql -uroot –p use test; DROP TABLE IF EXISTS t_pk; CREATE TABLE `t_pk` ( `id` int(11) NOT NULL AUTO_INCREMENT, `mem_id` int(11) NOT NULL , PRIMARY KEY (`id`), UNIQUE `uq_mem_id` (`mem_id`) ) ENGINE=InnoDB; INSERT INTO t_pk VALUES(1,1),(3,3),(5,5),(9,9),(11,11);
REPEATABLE READ(RR級別) | |
root@localhost[(none)]:10:04:34>use test; root@localhost[test]:10:04:41>prompt A>> PROMPT set to 'A>>' |
root@localhost[(none)]:10:04:37>use test; root@localhost[test]:10:04:52>prompt B>> PROMPT set to 'B>>' |
A>>select @@session.tx_isolation; +------------------------+ | @@session.tx_isolation | +------------------------+ | REPEATABLE-READ | +------------------------+ 1 row in set (0.01 sec) |
B>>select @@session.tx_isolation; +------------------------+ | @@session.tx_isolation | +------------------------+ | REPEATABLE-READ | +------------------------+ 1 row in set (0.00 sec) |
A>>begin; Query OK, 0 rows affected (0.00 sec) |
B>>begin; Query OK, 0 rows affected (0.00 sec) |
A>>select * from t_pk; +----+--------+ | id | mem_id | +----+--------+ | 1 | 1 | | 3 | 3 | | 5 | 5 | | 9 | 9 | | 11 | 11 | +----+--------+ 5 rows in set (0.00 sec) |
B>>select * from t_pk; +----+--------+ | id | mem_id | +----+--------+ | 1 | 1 | | 3 | 3 | | 5 | 5 | | 9 | 9 | | 11 | 11 | +----+--------+ 5 rows in set (0.00 sec) |
A>>select * from t_pk where mem_id=3 for update; +----+--------+ | id | mem_id | +----+--------+ | 3 | 3 | +----+--------+ 1 row in set (0.00 sec) |
|
B>>insert into t_pk values(2,2); Query OK, 1 row affected (0.00 sec) B>>insert into t_pk values(4,4); Query OK, 1 row affected (0.00 sec) B>>insert into t_pk values(3,3); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction B>>insert into t_pk values(5,5); ERROR 1062 (23000): Duplicate entry '5' for key 'PRIMARY' B>>insert into t_pk values(7,7); Query OK, 1 row affected (0.00 sec) |
|
結論:從這裏能夠看到,對於基於惟一索引的更新,MySQL只是鎖定了記錄自己。 同理,咱們能夠推導出主鍵也是同樣的。實驗的話我就略了,其實就是將上面的mem_id改爲id便可。 |
|
基於主鍵的Record Lock,仍是RR級別 | |
A>>rollback; Query OK, 0 rows affected (0.00 sec) |
B>>rollback; Query OK, 0 rows affected (0.00 sec) |
A>>begin; Query OK, 0 rows affected (0.00 sec |
B>>begin; Query OK, 0 rows affected (0.00 sec) |
A>>select * from t_pk where id=3 for update; +----+--------+ | id | mem_id | +----+--------+ | 3 | 3 | +----+--------+ 1 row in set (0.00 sec) |
|
B>>insert into t_pk values(2,2); Query OK, 1 row affected (0.00 sec) B>>insert into t_pk values(4,4); Query OK, 1 row affected (0.00 sec) |
|
結論:說明上面的推導正確。 | |
Read-Committed級別(RC) | |
A>>rollback; Query OK, 0 rows affected (0.00 sec) |
B>>rollback; Query OK, 0 rows affected (0.00 sec) |
A>>set @@session.tx_isolation="read-committed"; Query OK, 0 rows affected (0.01 sec) |
B>>set @@session.tx_isolation="read-committed"; Query OK, 0 rows affected (0.00 sec) |
A>>select @@session.tx_isolation; +------------------------+ | @@session.tx_isolation | +------------------------+ | READ-COMMITTED | +------------------------+ 1 row in set (0.00 sec) |
B>>select @@session.tx_isolation; +------------------------+ | @@session.tx_isolation | +------------------------+ | READ-COMMITTED | +------------------------+ 1 row in set (0.00 sec) |
A>>begin; Query OK, 0 rows affected (0.00 sec) |
B>>begin; Query OK, 0 rows affected (0.00 sec) |
A>>select * from t_pk; +----+--------+ | id | mem_id | +----+--------+ | 1 | 1 | | 3 | 3 | | 5 | 5 | | 9 | 9 | | 11 | 11 | +----+--------+ 5 rows in set (0.00 sec) |
B>>select * from t_pk; +----+--------+ | id | mem_id | +----+--------+ | 1 | 1 | | 3 | 3 | | 5 | 5 | | 9 | 9 | | 11 | 11 | +----+--------+ 5 rows in set (0.00 sec) |
A>>select * from t_pk where mem_id=3 for update; +----+--------+ | id | mem_id | +----+--------+ | 3 | 3 | +----+--------+ 1 row in set (0.00 sec) |
|
B>>insert into t_pk values(2,2); Query OK, 1 row affected (0.00 sec) B>>insert into t_pk values(4,4),(6,6),(10,10); Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0 |
|
結論:說明RC級別下,沒有間隙鎖存在。 |
主鍵+RR/RC
這跟惟一索引+RR/RC是同樣的,請參看上面的惟一索引+RR/RC。