mysql事務 鎖

簡介:mysql

在默認的狀況下,MySQL在自動提交(autocommit=1/ON)模式運行,這種模式會在每條語句執行完畢後把它做出的修改馬上提交給數據庫並使之永久化。事實上,這至關於把每一條語句都隱含地當作一個事務來執行。若是你想明確地執行事務,須要禁用自動提交模式並告訴MySQL你想讓它在什麼時候提交或回滾有關的修改。sql

咱們想要明確的執行事務的話,用START TRANSACTION(或BEGIN)開啓事務其實它會自動掛起自動提交事務模式(即會忽略autocommit=1),而後執行本次事務的各語句,最後用COMMIT語句結束事務並把它們作出的修改永久性記入數據庫。萬一事務過程當中發生錯誤,用一條ROLLBACK語句撤銷事務並把數據庫恢復到事務開始以前的狀態。數據庫

START TRANSACTION語句在COMMIT/ROLLBACK以後會作什麼?bash

答:在事務被提交或回滾以後,該模式將恢復到開始本次事務的START TRANSACTION語句被執行以前的狀態,這裏有兩種狀況:1.若是自動提交模式原來是激活的,結束事務將讓你回到自動提交模式session

           2.若是它原來是非自動提交模式的,結束當前事務將開始下一個事務併發

下面實例說明mvc

假若有表 t(a, b, c)主鍵爲a,b爲惟一索引,c爲常規字段測試

查看是否自動提交
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
可見以上爲自動提交

mysql> select * from t;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 1 |    1 |    1 |
| 2 |    2 |    2 |
+---+------+------+
2 rows in set (0.00 sec)

 

如下全爲自動模式下作的實驗:spa

1。查看自動提交模式數據提交狀況。鏈接1,鏈接2都未開啓事務

鏈接1 鏈接2
mysql> select * from t where b=2;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    2 |
+---+------+------+
1 row in set (0.00 sec)
 
 
mysql> select * from t where b=2;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    2 |
+---+------+------+
1 row in set (0.00 sec)
mysql> update t set c=3 where b=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from t where b=2; 
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    3 |
+---+------+------+
1 row in set (0.00 sec)
 
 
mysql> select * from t where b=2;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    3 |
+---+------+------+
1 row in set (0.00 sec)
可見別的鏈接已看到鏈接1的更改

 

2. 查看自動提交模式下start transaction的做用。鏈接1開啓事務,鏈接2未開啓

鏈接1 鏈接2
mysql> start transaction;             
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t where b=2;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    3 |
+---+------+------+
1 row in set (0.00 sec)
 
 
mysql> select * from t where b=2;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    3 |
+---+------+------+
1 row in set (0.00 sec)
mysql> update t set c=4 where b=2;  
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from t where b=2; 
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    4 |
+---+------+------+
1 row in set (0.00 sec)
可見自身事務下c已爲4
 
 
mysql> select * from t where b=2;  
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    3 |
+---+------+------+
1 row in set (0.00 sec)
可見鏈接2裏c還爲3,由於鏈接1裏的事務未提交
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t where b=2;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    4 |
+---+------+------+
1 row in set (0.00 sec)
 
 
mysql> select * from t where b=2;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    4 |
+---+------+------+
1 row in set (0.00 sec)
可見在鏈接1裏的事務提交後,這裏看到了最新的c

 

3.  鏈接1,鏈接2同時開啓事務時

鏈接1 鏈接2
mysql> start transaction;         
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t where b=2;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    4 |
+---+------+------+
1 row in set (0.00 sec)
 
 
mysql> start transaction;         
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t where b=2;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    4 |
+---+------+------+
1 row in set (0.00 sec)
mysql> update t set c=5 where b=2; 
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
 
 
mysql> update t set c=6 where b=2; 
....
此處會被掛起等待,由於鏈接1裏的事務已在b=2的
這條記錄上給加鎖了
此處有兩種狀況:
1. 鎖等待超時那麼會報錯
   ERROR 1205 (HY000): Lock wait timeout exceeded; 
try restarting transaction
2. 未超時的狀況,在鏈接1裏的事務執行完後,這個繼續執行

此處以未超時的狀況走,在鏈接1裏的事務commit後,
此處會自動提交update
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
 
 
Query OK, 0 rows affected (3.97 sec)
Rows matched: 1  Changed: 0  Warnings: 0
久違的提示信息呢。
mysql> select * from t where b=2; 
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    5 |
+---+------+------+
1 row in set (0.00 sec)
因爲鏈接2裏的事務未提交,此處查詢的還是5
 
 
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t where b=2;  
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    6 |
+---+------+------+
1 row in set (0.00 sec)
mysql> select * from t where b=2;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    6 |
+---+------+------+
1 row in set (0.00 sec)
可見已變
 

 

4. 假若有這樣的場景,鏈接1裏給c+1,鏈接2裏的給c-2

鏈接1 鏈接2
mysql> start transaction;            
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t where b=2;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    6 |
+---+------+------+
1 row in set (0.00 sec)
 
 
mysql> start transaction;            
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t where b=2;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    6 |
+---+------+------+
1 row in set (0.00 sec)
mysql> update t set c=c+1 where b=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from t where b=2;   
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    7 |
+---+------+------+
1 row in set (0.00 sec)
 
 
mysql> update t set c=c-2 where b=2;
...
此處會掛起等待
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t where b=2;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    7 |
+---+------+------+
1 row in set (0.00 sec)
 
 
Query OK, 1 row affected (21.60 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from t where b=2;    
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    5 |
+---+------+------+
1 row in set (0.00 sec)
可見這裏減2,已取到鏈接1裏的事務的更改,
剛纔的掛起等待就是爲了數據的惟一性
 
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t where b=2;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    5 |
+---+------+------+
1 row in set (0.00 sec)
mysql> select * from t where b=2;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    5 |
+---+------+------+
1 row in set (0.00 sec)
可見已爲5,原值6在通過+1,-2的操做後正確值爲5,對了
可是若是這裏不是c=c+1, c=c-2,而是c=x的方式,那麼就
有可能會覆蓋原值,因此在金額等的更改上,不該該取出
來再給數據庫賦值,而是在原基礎上進行加減。
 

 

5. 下面測試下死鎖狀況

鏈接1-事務1 鏈接2-事務2
mysql> start transaction;                   
Query OK, 0 rows affected (0.02 sec)
#由於b是惟一索引,因此這裏把b=2的記錄給加了個行
級鎖,若是查詢出的爲空的話,則全表加鎖,若是b不
是索引,則也是全表加鎖
mysql> select * from t where b=2 for update;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    5 |
+---+------+------+
1 row in set (0.02 sec)
 
 
mysql> start transaction;           
Query OK, 0 rows affected (0.02 sec)
此處
mysql> update t set c=2 where b=1;   
Query OK, 1 row affected (0.04 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> update t set c=3 where b=1;
...
此處阻塞住了
 
 
mysql> update t set c=6 where b=2; 
Query OK, 1 row affected (0.03 sec)
Rows matched: 1  Changed: 1  Warnings: 0
ERROR 1213 (40001): Deadlock found when trying 
to get lock; try restarting transaction
 
mysql> select * from t;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 1 |    1 |    1 |
| 2 |    2 |    5 |
+---+------+------+
2 rows in set (0.02 sec)
 
 
mysql> commit;
Query OK, 0 rows affected (0.03 sec)

mysql> select * from t;          
+---+------+------+
| a | b    | c    |
+---+------+------+
| 1 |    1 |    2 |
| 2 |    2 |    6 |
+---+------+------+
2 rows in set (0.02 sec)
mysql> select * from t;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 1 |    1 |    2 |
| 2 |    2 |    6 |
+---+------+------+
2 rows in set (0.04 sec)
可見update t set c=3 where b=1;這條語句沒有執行,
事務2的所有執行成功了,由於在死鎖發生時,事務1被
回滾了。具體參考下面的日誌信息
 

 

5.1  死鎖日誌分析

使用show engine innodb status\G查看死鎖信息,下面只摘取了死鎖信息部分,其餘的省略。.net

 

------------------------

LATEST DETECTED DEADLOCK

------------------------

2015-05-21 16:12:55 7fe02cfd2700

*** (1) TRANSACTION:  ## 事務1

TRANSACTION 7651536, ACTIVE 218 sec starting index read  ## 事務ID=7651536, 活躍了218秒

mysql tables in use 1, locked 1

LOCK WAIT 4 lock struct(s), heap size 1248, 3 row lock(s)  ## 有3個行鎖

MySQL thread id 192071, OS thread handle 0x7fe02ce0b700, query id 13896576 114.112.84.198 root updating  ## 該事務的線程ID=192071

update t set c=3 where b=1  ## 這是當前事務執行的SQL

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:  ##等待要加的鎖

RECORD LOCKS space id 3864 page no 4 n bits 72 index `ib` of table `test`.`t` trx id 7651536 lock_mode X locks rec but not gap waiting  ## ## 等待在惟一索引ib上的page num=4上加一個X鎖(lock_mode X locks rec but not gap)waiting意指等待的鎖

Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0  

 0: len 4; hex 80000001; asc     ;;

 1: len 4; hex 80000001; asc     ;;

 

*** (2) TRANSACTION:  ## 事務2

TRANSACTION 7651538, ACTIVE 200 sec starting index read  ## 事務ID=7651538, 活躍了200秒

mysql tables in use 1, locked 1

4 lock struct(s), heap size 1248, 3 row lock(s), undo log entries 1  ## 4個鎖,3個行鎖,1個undo log 

MySQL thread id 192072, OS thread handle 0x7fe02cfd2700, query id 13896591 114.112.84.198 root updating  ## 該事務的線程ID=192072 

update t set c=6 where b=2  ## 這是當前事務執行的SQL

*** (2) HOLDS THE LOCK(S):  ## 這個事務持有的鎖信息 

RECORD LOCKS space id 3864 page no 4 n bits 72 index `ib` of table `test`.`t` trx id 7651538 lock_mode X locks rec but not gap   ## 在惟一索引ib上page num=4上已持有一個X鎖

Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 

 0: len 4; hex 80000001; asc     ;;

 1: len 4; hex 80000001; asc     ;;

 

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:  ## 同時這個事務還等待的鎖信息

RECORD LOCKS space id 3864 page no 4 n bits 72 index `ib` of table `test`.`t` trx id 7651538 lock_mode X locks rec but not gap waiting  ## 一樣等待在惟一索引ib上的page num=4上加一個X鎖(lock_mode X locks rec but not gap

Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0  

 0: len 4; hex 80000002; asc     ;;

 1: len 4; hex 80000002; asc     ;;

 

*** WE ROLL BACK TRANSACTION (1)    ## 這裏選擇回滾了事務7651536

也就是事務7651536的sql沒有執行

update t set c=3 where b=1

事務7651538的sql執行了

update t set c=6 where b=2

 

6. for update實驗(for update是排它鎖X)

注意:若是autocommit爲on的狀態的話,必須手動begin開啓事務,不然for update鎖不住

off狀態的話會自動鎖(由於auto模式下sql執行完了就commit了)

鏈接1 - 事務1 鏈接2 - 事務2
mysql> start transaction;
Query OK, 0 rows affected (0.02 sec)

mysql> select * from t where b=2 for update;
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    6 |
+---+------+------+
1 row in set (0.03 sec)
 
 
mysql> start transaction;
Query OK, 0 rows affected (0.02 sec)

mysql> select * from t where b=2 for update;
...
此處阻塞住了
mysql> commit;
Query OK, 0 rows affected (0.04 sec)
 
 
+---+------+------+
| a | b    | c    |
+---+------+------+
| 2 |    2 |    6 |
+---+------+------+
1 row in set (26.91 sec)

 

7. for update範圍

有以下表:

MariaDB [t]> show create table t\G
*************************** 1. row ***************************
       Table: t
Create Table: CREATE TABLE `t` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `a` varchar(255) DEFAULT NULL,
  `b` varchar(255) DEFAULT NULL,
  `c` int(10) DEFAULT NULL,
  `d` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_b` (`b`) USING BTREE,
  KEY `index_a` (`a`) USING BTREE,
  KEY `index_c` (`c`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)


MariaDB [t]> SHOW VARIABLES LIKE '%AUTOCOMMIT%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.00 sec)

MariaDB [t]> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ       |
+-----------------------+
1 row in set (0.00 sec)

MariaDB [t]> select * from t;
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  1 | 1    | 1    |    1 | 1    |
|  2 | 2    | 2    |    2 | 2    |
|  3 | 3    | 3    |    3 | 3    |
|  4 | 4    | 4    |    4 | 4    |
|  5 | 5    | 5    |    5 | 5    |
|  6 | 1    | 6    |    1 | 2    |
|  7 | 1    | 7    |    1 | 2    |
+----+------+------+------+------+
7 rows in set (0.00 sec)

 

7.1 事務1開事務,事務2開事務/不開事務

事務1 事務2
MariaDB [t]> begin;
Query OK, 0 rows affected (0.00 sec)

MariaDB [t]> select * from t where id=1 
for update;
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  1 | 1    | 1    | 1    | 1    |
+----+------+------+------+------+
1 row in set (0.00 sec)

 

 
 
# 未開啓事務
MariaDB [t]> select * from t where id=1 lock 
in share mode;
ERROR 1205 (HY000): Lock wait timeout exceeded; 
try restarting transaction

MariaDB [t]> select * from t where id=1 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; 
try restarting transaction

MariaDB [t]> delete from t where id=1;   
ERROR 1205 (HY000): Lock wait timeout exceeded; 
try restarting transaction

MariaDB [t]> update t set d=1 where id=1;
ERROR 1205 (HY000): Lock wait timeout exceeded; 
try restarting transaction

MariaDB [t]> delete from t where id=2;  
Query OK, 1 row affected (0.00 sec)

MariaDB [t]> update t set d=1 where id=3;
Query OK, 1 row affected (0.00 sec)

# 開啓事務
MariaDB [t]> begin;
Query OK, 0 rows affected (0.00 sec)
MariaDB [t]> select * from t where id=1 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; 
try restarting transaction

手動開啓事務後和上面結果同樣,由於autocommit雖然自動commit,
但也得先拿鎖,拿鎖就得等鎖釋放

 

7.2 事務1不開事務,事務2開事務/不開事務

事務1 事務2
MariaDB [t]> select * from t where id=1 for update;
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  1 | 1    | 1    | 1    | 1    |
+----+------+------+------+------+
1 row in set (0.00 sec)

autocommit下自動commit,因此右邊的可拿到鎖,
因此for update必須在開啓事務的前提下使用

 

 
 
MariaDB [t]> select * from t where id=1 
for update;
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  1 | 1    | 1    | 1    | 1    |
+----+------+------+------+------+
1 row in set (0.00 sec)
事務2沒開事務,能查到


MariaDB [t]> begin;
Query OK, 0 rows affected (0.00 sec)

MariaDB [t]> select * from t where id=1 
for update;
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  1 | 1    | 1    | 1    | 1    |
+----+------+------+------+------+
1 row in set (0.00 sec)
事務2開事務,仍是能查到

 

   

 

7.3 主鍵索引鎖一條,當查詢不到記錄時,不鎖任何記錄

事務1 事務2
MariaDB [t]> begin;
Query OK, 0 rows affected (0.00 sec)

MariaDB [t]> select * from t where id=1 
for update;
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  1 | 1    | 1    | 1    | 1    |
+----+------+------+------+------+
1 row in set (0.00 sec)

 

 
 
MariaDB [t]> begin;
Query OK, 0 rows affected (0.00 sec)

MariaDB [t]> select * from t where id=1 
for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; 
try restarting transaction
可見,id=1的被鎖住了

MariaDB [t]> select * from t where id=3 
for update;  
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  3 | 3    | 3    |    3 | 1    |
+----+------+------+------+------+
1 row in set (0.00 sec)
可見id=3的沒被鎖住,說明主鍵只鎖一條

MariaDB [t]> insert into t(a,b,c,d) 
values(1,2,3,3);
Query OK, 1 row affected (0.01 sec)

MariaDB [t]> select * from t;
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  1 | 1    | 1    |    1 | 1    |
|  3 | 3    | 3    |    3 | 1    |
|  4 | 4    | 4    |    4 | 4    |
|  5 | 5    | 5    |    5 | 5    |
|  6 | 1    | 6    |    1 | 2    |
|  7 | 1    | 7    |    1 | 2    |
|  9 | 1    | 2    |    3 | 3    |
+----+------+------+------+------+
7 rows in set (0.00 sec)

MariaDB [t]> delete from t where id=9;
Query OK, 1 row affected (0.00 sec)

MariaDB [t]> select * from t;         
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  1 | 1    | 1    |    1 | 1    |
|  3 | 3    | 3    |    3 | 1    |
|  4 | 4    | 4    |    4 | 4    |
|  5 | 5    | 5    |    5 | 5    |
|  6 | 1    | 6    |    1 | 2    |
|  7 | 1    | 7    |    1 | 2    |
+----+------+------+------+------+
6 rows in set (0.00 sec)

MariaDB [t]> update t set d=3 where id=3; 
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

MariaDB [t]> select * from t;
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  1 | 1    | 1    |    1 | 1    |
|  3 | 3    | 3    |    3 | 3    |
|  4 | 4    | 4    |    4 | 4    |
|  5 | 5    | 5    |    5 | 5    |
|  6 | 1    | 6    |    1 | 2    |
|  7 | 1    | 7    |    1 | 2    |
+----+------+------+------+------+
6 rows in set (0.00 sec)

 

如下爲間隙鎖講解 

MariaDB [t]> begin;
Query OK, 0 rows affected (0.00 sec)

MariaDB [t]> select * from t where id=100 
for update;    
Empty set (0.01 sec)

MariaDB [t]> SHOW TABLE STATUS\G
*************************** 1. row *****
           Name: t
         Engine: InnoDB
        Version: 10
     Row_format: Compact
           Rows: 7
 Avg_row_length: 2340
    Data_length: 16384
Max_data_length: 0
   Index_length: 49152
      Data_free: 0
 Auto_increment: 12 ----當前自增id最大值
    Create_time: 2017-08-24 14:34:25
    Update_time: NULL
     Check_time: NULL
      Collation: utf8_general_ci
       Checksum: NULL
 Create_options: 
        Comment: 
1 row in set (0.00 sec)

當前獲取到next-key間隙鎖
id是自增索引,當前還未自增到100,因此有可能
新插入數據的id爲100,
爲了確保沒有幻讀,加了(12,max)的間隙鎖

 

 
 
MariaDB [t]> begin;
Query OK, 0 rows affected (0.00 sec)

MariaDB [t]> select * from t where id=1 for update;
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  1 | 1    | 1    | 1    | 1    |
+----+------+------+------+------+
1 row in set (0.01 sec)

由於鎖住的是(12,max),因此前面的數據沒有鎖,當前可拿到鎖

MariaDB [t]> insert into t(a,b,c,d) values(1,2,3,3);
ERROR 1205 (HY000): Lock wait timeout exceeded; 
try restarting transaction

由於鎖住的是(12,max),因此後面的被鎖住,不能插入

 

假如以下:
MariaDB [t]> select * from t where id=2 
for update;
Empty set (0.00 sec)

自增id不會再等於2,因此沒有鎖任何記錄

 

 
 
MariaDB [t]> begin;
Query OK, 0 rows affected (0.00 sec)

MariaDB [t]> select * from t where id=1 for update;
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  1 | 1    | 1    |    1 | 1    |
+----+------+------+------+------+
1 row in set (0.00 sec)

MariaDB [t]> update t set d=3 where id=3; 
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0

MariaDB [t]> delete from t where id=3;
Query OK, 1 row affected (0.00 sec)

MariaDB [t]> insert into t(a,b,c,d) values(8,8,8,8); 
Query OK, 1 row affected (0.00 sec)

MariaDB [t]> rollback;
Query OK, 0 rows affected (0.00 sec)

可見可操做任何操做

 

7.4 非主鍵索引查詢,會鎖住查詢的記錄,其他不鎖

事務1:

MariaDB [t]> begin;
Query OK, 0 rows affected (0.01 sec)

MariaDB [t]> select * from t where a='1' for update;  
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  1 | 1    | 1    | 1    | 1    |
|  6 | 1    | 1    | 1    | 2    |
|  7 | 1    | 2    | 2    | 2    |
+----+------+------+------+------+
3 rows in set (0.00 sec)

MariaDB [t]> explain select * from t where a='1' for update;
+------+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
| id   | select_type | table | type | possible_keys | key     | key_len | ref   | rows | Extra                 |
+------+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
|    1 | SIMPLE      | t     | ref  | index_a       | index_a | 768     | const |    3 | Using index condition |
+------+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+

查看查詢計劃,用的索引,因此鎖全表記錄

必定要注意:varchar的索引若是查詢時不用''單引號包起來不會走索引
MariaDB [t]> explain select * from t where a=1 for update;   
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id   | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
|    1 | SIMPLE      | t     | ALL  | index_a       | NULL | NULL    | NULL |   17 | Using where |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+

事務2:

MariaDB [t]> begin;
Query OK, 0 rows affected (0.00 sec)

MariaDB [t]> select * from t where a=1 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [t]> select * from t where a='1' for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [t]> select * from t where a='3' for update; 
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  3 | 3    | 3    |    3 | 3    |
+----+------+------+------+------+
1 row in set (0.00 sec)
MariaDB [t]> select * from t where b='1' for update; 
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [t]> select * from t where b='3' for update; 
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  3 | 3    | 3    |    3 | 3    |
+----+------+------+------+------+
1 row in set (0.00 sec)
MariaDB [t]> select * from t where id=1 for update; 
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [t]> select * from t where id=3 for update;      
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  3 | 3    | 3    |    3 | 3    |
+----+------+------+------+------+
1 row in set (0.00 sec)
可見,只鎖住了查詢出來的3條記錄,其他都未加鎖

MariaDB [t]> insert into t(a,b,c,d) values('1','2',3,'3');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [t]> insert into t(a,b,c,d) values('2','2',3,'3');
Query OK, 1 row affected (0.00 sec)
可見插入時,a爲'1'值的記錄插入不進去,其他均可以

MariaDB [t]> update t set d='3' where id=1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [t]> update t set d='3' where id=3;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0
可見修改記錄時也是,若是此條記錄被鎖住的話須要等待鎖

7.5 非索引字段鎖所有記錄

事務1

MariaDB [t]> begin;
Query OK, 0 rows affected (0.00 sec)

MariaDB [t]> select * from t where d='1' for update;         
+----+------+------+------+------+
| id | a    | b    | c    | d    |
+----+------+------+------+------+
|  1 | 1    | 1    |    1 | 1    |
+----+------+------+------+------+
1 row in set (0.00 sec)

MariaDB [t]> explain select * from t where d='1' for update;
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id   | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
|    1 | SIMPLE      | t     | ALL  | NULL          | NULL | NULL    | NULL |   18 | Using where |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
可見全表掃描

事務2

MariaDB [t]> begin;
Query OK, 0 rows affected (0.00 sec)

MariaDB [t]> select * from t where a='1' for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [t]> select * from t where a='1' for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [t]> select * from t where a='3' for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [t]> select * from t where b='1' for update; 
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [t]> select * from t where id=1 for update; 
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [t]> select * from t where id=3 for update; 
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
非主鍵索引時鎖全表的記錄,其餘記錄都獲取不到鎖(包括主鍵索引),因此一直等待到超時

MariaDB [t]> insert into t(a,b,c,d) values('1','2',3,'3');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
非主鍵索引時鎖全表的記錄,可見插入不可插

MariaDB [t]> update t set d='3' where id=3;
修改記錄也是不行的

8.幻讀

在可重複讀的隔離級別下,能夠解決不可重複讀,以及幻讀(按定義是有幻讀的,但mysql經過mvcc解決了(一致性讀是經過 MVCC 爲查詢提供了一個基於時間的點的快照。這個查詢只能看到在本身以前提交的數據,而在查詢開始以後提交的數據是不能夠看到的。在默認隔離級別REPEATABLE READ下,同一事務的全部一致性讀只會讀取第一次查詢時建立的快照), 但加鎖讀仍是能夠讀到的),見下

MariaDB [demo]> desc user;
+-------------+--------------+------+-----+---------+-------+
| Field       | Type         | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+-------+
| id          | varchar(50)  | NO   | PRI | NULL    |       |
| name        | varchar(255) | YES  |     | NULL    |       |
| create_time | datetime     | YES  |     | NULL    |       |
+-------------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
事務1 事務2
MariaDB [demo]> begin;
Query OK, 0 rows affected (0.00 sec)

MariaDB [demo]> select * from user;
+----+------+---------------------+
| id | name | create_time         |
+----+------+---------------------+
| zq | zq1  | 2017-12-05 18:47:26 |
+----+------+---------------------+
1 row in set (0.00 sec)

 

 
 
MariaDB [demo]> begin;
Query OK, 0 rows affected (0.00 sec)

MariaDB [demo]> select * from user;
+----+------+---------------------+
| id | name | create_time         |
+----+------+---------------------+
| zq | zq1  | 2017-12-05 18:47:26 |
+----+------+---------------------+
1 row in set (0.00 sec)

 

MariaDB [demo]> insert into user(id, name) 
values("zq2", "zq2");
Query OK, 1 row affected (0.00 sec)

MariaDB [demo]> select * from user;
+-----+------+---------------------+
| id  | name | create_time         |
+-----+------+---------------------+
| zq  | zq1  | 2017-12-05 18:47:26 |
| zq2 | zq2  | NULL                |
+-----+------+---------------------+
2 rows in set (0.00 sec)

 

 
 
MariaDB [demo]> select * from user;
+----+------+---------------------+
| id | name | create_time         |
+----+------+---------------------+
| zq | zq1  | 2017-12-05 18:47:26 |
+----+------+---------------------+
1 row in set (0.00 sec)

此處查看不到事務1未commit的數據,解決髒讀

 

MariaDB [demo]> commit;
Query OK, 0 rows affected (0.02 sec)

 

 
 
MariaDB [demo]> select * from user;
+----+------+---------------------+
| id | name | create_time         |
+----+------+---------------------+
| zq | zq1  | 2017-12-05 18:47:26 |
+----+------+---------------------+
1 row in set (0.00 sec)
此處事務1已commit數據,但仍是看不到,保證
了可重複復讀以及幻讀問題

 

 
MariaDB [demo]> select * from user for update;
+-----+------+---------------------+
| id  | name | create_time         |
+-----+------+---------------------+
| zq  | zq1  | 2017-12-05 18:47:26 |
| zq2 | zq2  | NULL                |
+-----+------+---------------------+
2 rows in set (0.00 sec)
但若是加鎖讀的話是能看到的
其實,可重複讀和提交讀是矛盾的。在同一個事務裏,
若是保證了可重複讀,就會看不到其餘事務的提交,
違背了提交讀;若是保證了提交讀,
就會致使先後兩次讀到的結果不一致,違背了可重複讀。

InnoDB提供了這樣的機制,在默認的可重複讀的隔離級別裏,
可使用加鎖讀去查詢最新的數據。

這時就會對當前讀數據加鎖,像查全表是加所有鎖

 

 
MariaDB [demo]> commit;
Query OK, 0 rows affected (0.01 sec)

 

 

8.1 那就來詳細分析下RR隔離級別下是如何防止幻讀的

幻讀問題是指一個事務的兩次不一樣時間的相同查詢返回了不一樣的的結果集。例如:一個 select 語句執行了兩次,可是在第二次返回了第一次沒有返回的行,那麼這些行就是「phantom」 row.

read view(或者說 快照讀 或者說MVCC)實現了一致性不鎖定讀(Consistent Nonlocking Reads),從而避免了幻讀

深一點這裏講解也不錯

首先讀分爲: 
快照讀 
select * from table where ?;

當前讀:特殊的讀操做,插入/更新/刪除操做,屬於當前讀,須要加鎖。 
select * from table where ? lock in share mode; 
select * from table where ? for update; 
insert into table values (…); 
update table set ? where ?; 
delete from table where ?;

全部以上的語句,都屬於當前讀,讀取記錄的最新版本。而且,讀取以後,還須要保證其餘併發事務不能修改當前記錄,對讀取記錄加鎖。其中,除了第一條語句,對讀取記錄加S鎖 (共享鎖)外,其餘的操做,都加的是X鎖 (排它鎖)。

對於快照讀來講,幻讀的解決是依賴mvcc解決。而對於當前讀則依賴於gap-lock解決(由於間隙鎖會鎖定當前查詢間隙中暫時不存在的數據,其餘事務想插入或刪除或修改都會有當前這個間隙鎖而被hang住)。

須要注意的是快照讀是在select查詢後而不是事務開啓後

看下面一個異常現象(也不算異常了)

SESSION_A開始事務並建立快照,或一個普通的查詢也會建立一個快照讀
SESSION_A>START TRANSACTION WITH CONSISTENT SNAPSHOT;
Query OK, 0 rows affected (0.00 sec)

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


SESSION_A>select * from read_view;
+-------------------------+
| text                    |
+-------------------------+
| init                    |
| after session A select  |
| before Session_A select |
+-------------------------+
3 rows in set (0.00 sec)


SESSION_B>insert into read_view values('anomaly'),('anomaly');
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

SESSION_B>update read_view set text='INIT' where text='init';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

SESSION_B>commit;
Query OK, 0 rows affected (0.00 sec)

SESSION_A>select * from read_view;
+-------------------------+
| text                    |
+-------------------------+
| init                    |
| after session A select  |
| before Session_A select |
+-------------------------+
3 rows in set (0.00 sec)

# SESSION_A更新了它並無"看"到的行
SESSION_A>update read_view set text='anomaly!' where text='anomaly';
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2  Changed: 2  Warnings: 0

# 這裏竟然看到了SESSION_B commit的數據了
SESSION_A>select * from read_view;
+-------------------------+
| text                    |
+-------------------------+
| init                    |
| after session A select  |
| before Session_A select |
| anomaly!                |
| anomaly!                |
+-------------------------+
5 rows in set (0.00 sec)

SESSION_A>commit;
Query OK, 0 rows affected (0.00 sec)

SESSION_A>select * from read_view;
+-------------------------+
| text                    |
+-------------------------+
| INIT                    |
| after session A select  |
| before Session_A select |
| anomaly!                |
| anomaly!                |
+-------------------------+
5 rows in set (0.00 sec)

觀察實驗步驟能夠發現,在倒數第二次查詢中,出現了一個並不存在的狀態,這裏A的先後兩次讀,均爲快照讀,並且是在同一個事務中。可是B先插入直接提交,此時A再update,update屬於當前讀,因此能夠做用於新插入的行,而且將修改行的當前版本號設爲A的事務號,因此第二次的快照讀,是能夠讀取到的,由於同事務號。這種狀況符合MVCC的規則,若是要稱爲一種幻讀也非不可,算爲一個特殊狀況來看待吧。

InnoDB經過Nextkey lock解決了當前讀時的幻讀問題,就是讀取時採用當前讀

Innodb行鎖分爲:

類型 說明
Record Lock: 在索引上對單行記錄加鎖.
Gap Lock: 鎖定一個範圍的記錄,但不包括記錄自己.鎖加在未使用的空閒空間上,多是兩個索引記錄之間,也多是第一個索引記錄以前或最後一個索引以後的空間.
Next-Key Lock: 行鎖與間隙鎖組合起來用就叫作Next-Key Lock。鎖定一個範圍,而且鎖定記錄自己。對於行的查詢,都是採用該方法,主要目的是解決幻讀的問題。
 

假若有下

(mysql@localhost) [fandb]> create table t5(id int,key(id));
Query OK, 0 rows affected (0.02 sec)

(mysql@localhost) [fandb]> insert into t5 values(1),(4),(7),(10);
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

 開始實驗

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

SESSION_A>select * from t5;
+------+
| id   |
+------+
|    1 |
|    4 |
|    7 |
|   10 |
+------+
4 rows in set (0.00 sec)

SESSION_A>select * from t5 where id=7 for update;
+------+
| id   |
+------+
|    7 |
+------+
1 row in set (0.00 sec)


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

SESSION_B>insert into t5 values(2);
Query OK, 1 row affected (0.00 sec)

SESSION_B>insert into t5 values(12);
Query OK, 1 row affected (0.00 sec)

SESSION_B>insert into t5 values(4); --被阻塞
^CCtrl-C -- sending "KILL QUERY 93" to server ...
Ctrl-C -- query aborted.
^[[AERROR 1317 (70100): Query execution was interrupted

SESSION_B>insert into t5 values(5); --被阻塞
^CCtrl-C -- sending "KILL QUERY 93" to server ...
Ctrl-C -- query aborted.
^[[AERROR 1317 (70100): Query execution was interrupted

SESSION_B>insert into t5 values(7); --被阻塞
^CCtrl-C -- sending "KILL QUERY 93" to server ...
Ctrl-C -- query aborted.
ERROR 1317 (70100): Query execution was interrupted

SESSION_B>insert into t5 values(9); --被阻塞
^CCtrl-C -- sending "KILL QUERY 93" to server ...
Ctrl-C -- query aborted.
ERROR 1317 (70100): Query execution was interrupted

SESSION_B>insert into t5 values(10);
Query OK, 1 row affected (0.00 sec)


SESSION_B>commit;
Query OK, 0 rows affected (0.00 sec)


SESSION_A>select * from t5;
+------+
| id   |
+------+
|    1 |
|    4 |
|    7 |
|   10 |
+------+
4 rows in set (0.00 sec)

SESSION_A>commit;
Query OK, 0 rows affected (0.00 sec)

SESSION_A>select * from t5;
+------+
| id   |
+------+
|    1 |
|    2 |
|    4 |
|    7 |
|   10 |
|   10 |
|   12 |
+------+
6 rows in set (0.00 sec)

當以當前讀模式select * from t5 where id=7 for update;獲取 id=7的數據時,產生了 Next-Key Lock,鎖住了4-10範圍和 id=7單個record 
從而阻塞了 SESSION_B在這個範圍內插入數據,而在除此以外的範圍內是能夠插入數據的。 
在倒數第二個查詢中,由於 read view 的存在,避免了咱們看到 2和12兩條數據,避免了幻讀 
同時由於 Next-Key Lock 的存在,阻塞了其餘會話插入數據,所以當前模式讀不會產生幻讀(select for update 是以當前讀模式獲取數據)

儘可能使用惟一索引,由於惟一索引會把Next-Key Lock降級爲Record Lock

相關文章
相關標籤/搜索