MySQL 事務的隔離級別及鎖操作演示

MySQL 版本:5.7

安裝環境:MAC OS

一、測試數據

測試數據庫:test;測試表:tt

CREATE TABLE `tt` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(100) DEFAULT NULL,
  KEY `name_idx` (`name`),
  KEY `id_idx` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 

插入測試數據:

insert into tt value(1, "a”);
insert into tt value(1, "b”);
insert into tt value(2, “b");

二、數據庫服務設置

1、事務隔離級別設置

mysql> set global transaction_isolation = 'repeatable-read';
Query OK, 0 rows affected (0.00 sec)
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-UNCOMMITTED        |
+-------------------------+

2、鎖等待時間設置

mysql> set global innodb_lock_wait_timeout=5;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select @@innodb_lock_wait_timeout;
+----------------------------+
| @@innodb_lock_wait_timeout |
+----------------------------+
|                          5 |
+----------------------------+

3、附註

更改設置後,後續開啓的連接 Session 纔會生效。 

三、讀未提交(READ-UNCOMMITTED)

開啓兩個連接 Session: 

Session 1 Session 2

開啓事務,更新 id 爲 2 的記錄 name 爲 “ss" ,保持事務未提交:

Query OK, 0 rows affected (0.00 sec)
mysql> select * from tt;
+------+------+
| id   | name |
+------+------+
|    1 | a    |
|    1 | b    |
|    2 | b    |
+------+------+
3 rows in set (0.00 sec)
mysql> update tt set name = 'ss' where id = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
 
 

開啓事務,查詢 id 爲 2 的記錄 name 值:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from tt where id = 2;
+------+------+
| id   | name |
+------+------+
|    2 | ss   |
+------+———+ 

事務 2 可以查詢到事務 1 未提交的數據變更。對於事務 2 來說,這條數據是髒數據。

四、讀已提交(READ-COMMITTED)

解決 READ-UNCOMMITTED 隔離級別下產生的髒讀現象。

設置事務隔離級別:

mysql> set global transaction_isolation = 'read-committed';
Query OK, 0 rows affected (0.00 sec)

重新開啓測試 Session,查詢事務隔離級別:

mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-COMMITTED          |
+-------------------------+
 
Session 1 Session 2
開啓事務,更新 id 爲 2 的記錄 name 爲 “ssr”:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> update tt set name = 'ssr' where id = 2;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
 
mysql> select * from tt where id = 2;
+------+------+
| id   | name |
+------+------+
|    2 | ssr  |
+------+------+
1 row in set (0.01 sec)
 
 
查詢數據,無法查詢到 事務 1 未提交的數據:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select * from tt where id = 2;
+------+------+
| id   | name |
+------+------+
|    2 | b    |
+------+------+
1 row in set (0.00 sec) 
提交事務:
mysql> commit ;
Query OK, 0 rows affected (0.01 sec)
 
 
查詢數據,得到的是事務 1 中已提交的數據變更:
mysql> select * from tt where id = 2;
+------+------+
| id   | name |
+------+------+
|    2 | ssr    |
+------+------+
1 row in set (0.00 sec)
 
對於事務 2 來說,在事務 1 提交前後,獲取到的數據是不一樣的,即不可重複讀問題。

五、可重複讀(REPEATABLE-READ)

解決 READ-COMMITTED 隔離級別下產生的不可重複讀現象。

Session 1中 設置事務隔離級別: 

mysql> set global transaction_isolation = 'repeatable-read';
Query OK, 0 rows affected (0.01 sec)

重新開啓事務,查詢隔離級別:

mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ         |
+-------------------------+
1 row in set (0.00 sec)

 

Session 1 Session 2

 

Session 2 開啓事務,查詢數據:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
Database changed
mysql> select * from tt where id = 2;
+------+------+
| id   | name |
+------+------+
|    2 | b    |
+------+------+
1 row in set (0.00 sec)

更新 id 爲 2 的記錄 name 爲 “ssrr”, 並提交事務:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update tt set name = 'ssrr' where id = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from tt where id = 2;
+------+------+
| id   | name |
+------+------+
|    2 | ssrr |
+------+------+
1 row in set (0.00 sec)  

 

 

Session 2 重新查詢數據:

mysql> select * from tt where id = 2;
+------+------+
| id   | name |
+------+------+
|    2 | b    |
+------+------+

當前數據未變。

但是問題是,事務 1 已經進行了數據變更,並且提交,事務 2 無法獲取所查記錄最新變更信息。

爲什麼事務 2 前後兩次相同查詢所得的數據是一樣的?

一致性讀(consistent read)查詢模式:基於【某一時刻】的【數據快照】提供讀查詢結果。無論查詢的數據是否被其它事務所改變。這個【某一時刻】在 repeatable-read 隔離級別下爲事務中第一次執行查詢操作的時間點,read-committed 隔離級別下,數據快照會在每一次執行一致性讀操作時進行重置。

幻讀

如何避免:加X鎖

Next-key lock:Record lock + Gap lock

六、關於 Next-key lock 加鎖

調整表 tt 索引及數據:

mysql> show create table tt;
+-------+-------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                      |
+-------+-------------------------------------------------------+
| tt    | CREATE TABLE `tt` (
  `id` int(11) NOT NULL,
  `name` varchar(100) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_age` (`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+-------+-------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select * from tt;
+-----+------+------+
| id  | name | age  |
+-----+------+------+
|  90 | aa   |   10 |
| 102 | bb   |   15 |
| 108 | cc   |   20 |
| 130 | dd   |   25 |
| 150 | ee   |   30 |
+-----+------+------+

1、等值條件

對於使用唯一性索引:加的鎖爲 Record lock

Session 1 Session 2

開啓事務,查詢 id 爲 108 記錄加 X lock:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select * from tt where id = 108 for update;
+-----+------+------+
| id  | name | age  |
+-----+------+------+
| 108 | cc   |   20 |
+-----+------+------+
1 row in set (0.01 sec)
 
 

開啓事務,記錄前後緊鄰 gap 插入記錄:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into tt value(106, 'bc', 16);
Query OK, 1 row affected (0.00 sec)
 
mysql> insert into tt value(110, 'cd', 22);
Query OK, 1 row affected (0.00 sec)

記錄均可成功插入

對於使用非唯一性索引:加的鎖爲 Record lock + Gap lock 前後緊鄰 gap

:首先加鎖 (15, 20],因爲是非唯一索引,繼續向後查找到第一個不滿足條件的元素 25 加 gap lock (20, 25)

Session 1 Session 2

開啓事務,查詢 age 爲 20 記錄加 X lock:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select * from tt where age = 20 for update;
+-----+------+------+
| id  | name | age  |
+-----+------+------+
| 108 | cc   |   20 |
+-----+------+------+
1 row in set (0.00 sec)
 
 

開啓事務,記錄緊鄰前後 gap 插入記錄:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into tt value(106, 'bc', 18);
 
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
 
mysql> insert into tt value(110, 'cd', 22);
 
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

操作均被 block。

緊鄰 gap 以外插入記錄:

mysql> insert into tt value(100, 'ab', 12);
Query OK, 1 row affected (0.00 sec)
 
mysql> insert into tt value(140, 'de', 27);
Query OK, 1 row affected (0.00 sec)

記錄均可成功插入

對於不使用索引的:加鎖爲全部記錄及gap 

Session1
Session2

開啓事務,查詢 name 爲 ‘cc’ 記錄加 X lock:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select * from tt where name = 'cc' for update;
+-----+------+------+
| id  | name | age  |
+-----+------+------+
| 108 | cc   |   20 |
+-----+------+------+
 
 

開啓事務,各個間隙嘗試插入記錄:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into tt value(80, 'pa', 5);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into tt value(95, 'ab', 13);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into tt value(105, 'bc', 18);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into tt value(120, 'cd', 23);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into tt value(140, 'de', 28);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into tt value(160, 'en', 35);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

操作均被 block。

更新記錄:

mysql> update tt set age = 21 where name = 'cc';
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update tt set age = 16 where name = 'bb';
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

操作均被 block。

2、範圍條件

使用唯一索引:

Session 1 Session 2

開啓事務,查詢 id 爲 108 的記錄用以更新

mysql> select * from tt where id >= 108 and id < 109 for update;
+-----+------+------+
| id  | name | age  |
+-----+------+------+
| 108 | cc   |   20 |
+-----+------+------+
1 row in set (0.01 sec)
 
 

開啓事務,間隙插入記錄:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into tt value(120, 'cd', 23);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
 
mysql> update tt set age = 26 where id = 130;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

操作被 block。

加鎖說明:

>= 108 條件:主鍵索引加鎖爲 Record lock 記錄 108

< 109 條件:因爲不存在 109 記錄,所以繼續向右遍歷至 130 不滿足,加鎖 (108, 130]

重新開啓事務,右側條件改爲開區間 130:

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
 
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select * from tt where id >= 108 and id < 130 for update;
+-----+------+------+
| id  | name | age  |
+-----+------+------+
| 108 | cc   |   20 |
+-----+------+------+
1 row in set (0.01 sec)
 
 

開啓事務,操作 130 後間隙插入記錄及更新 id 爲 130 記錄:

mysql> insert into tt value(135, 'ce', 32);
Query OK, 1 row affected (0.01 sec)
 
mysql> rollback;
Query OK, 0 rows affected (0.01 sec)
 
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> update tt set age = 26 where id = 130;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

插入操作成功,更新操作被 block。

加鎖說明:

< 130 條件:存在 130 記錄,加鎖 (108, 130]

重新開啓事務,右側條件改爲閉區間 130

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
 
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select * from tt where id >= 108 and id <= 130 for update;
 
+-----+------+------+
| id  | name | age  |
+-----+------+------+
| 108 | cc   |   20 |
| 130 | dd   |   25 |
+-----+------+------+
 
 

開啓事務,操作 130 後間隙插入記錄及更新 id 爲 150 記錄:

mysql> rollback;
Query OK, 0 rows affected (0.01 sec)
 
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into tt value(140, 'de', 28);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update tt set age = 35 where id = 150;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

插入操作成功,更新操作被 block。

加鎖說明:

<= 130 條件:存在 130 記錄,加鎖 (108, 130],繼續向右查詢到不滿足條件記錄 150,加鎖 (130, 150] 

 2、使用非唯一索引

Session 1 Session 2

開啓事務,查詢 age 範圍記錄用以更新:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select * from tt where age >= 20 and age < 21 for update;
+-----+------+------+
| id  | name | age  |
+-----+------+------+
| 108 | cc   |   20 |
+-----+------+------+
 
 

開啓事務,間隙插入記錄:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into tt value(105, 'bc', 18);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into tt value(120, 'cd', 23);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into tt value(140, 'de', 28);
Query OK, 1 row affected (0.00 sec)
 
mysql> update tt set name = 'test' where age = 25;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

加鎖說明:

>= 20 條件:非唯一索引,加鎖爲 (15, 20]

< 21 條件:因爲不存在 21 記錄,所以繼續向右遍歷至 25 不滿足,加鎖 (20, 25]

重新開啓事務,右側條件改爲開區間 25

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
 
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select * from tt where age >= 20 and age < 25 for update;
+-----+------+------+
| id  | name | age  |
+-----+------+------+
| 108 | cc   |   20 |
+-----+------+------+
 
 

開啓事務,操作 25 後間隙插入記錄及更新 id 爲 25 記錄:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into tt value(140, 'de', 28);
Query OK, 1 row affected (0.00 sec)
 
mysql> update tt set name = 'test' where age = 25;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

插入操作成功,更新操作被 block。

加鎖說明:

< 25 條件:存在 25 記錄,加鎖 (20, 25]

重新開啓事務,右側條件改爲閉區間 25

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
 
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select * from tt where age >= 20 and age <= 25 for update;
+-----+------+------+
| id  | name | age  |
+-----+------+------+
| 108 | cc   |   20 |
| 130 | dd   |   25 |
+-----+------+------+
 
 

開啓事務,操作 25 後間隙插入記錄及更新 age 爲 30 記錄:

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
 
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into tt value(140, 'de', 28);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update tt set name = 'test' where age = 30;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

插入操作成功,更新操作被 block。

加鎖說明:

<= 25 條件:存在 25 記錄,加鎖 (20, 25],繼續向右查詢到不滿足條件記錄 30,加鎖 (25, 30] 

3、 不使用索引

Session 1 Session 2

開啓事務,查詢 name 範圍記錄用以更新:

mysql> select * from tt where name >= 'cc' and name < 'dd' for update;
+-----+------+------+
| id  | name | age  |
+-----+------+------+
| 108 | cc   |   20 |
+-----+------+------+
 
 

開啓事務,間隙插入記錄:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into tt value(85, 'pa', 5);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into tt value(95, 'ab', 13);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into tt value(105, 'bc', 18);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into tt value(120, 'cd', 23);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into tt value(140, 'de', 28);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into tt value(160, 'et', 50);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

加鎖說明:

間隙全部加鎖

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