天天看點

MySQL事務隔離級别解密

  1. READ UNCOMMITTED 未送出讀 

    在READ UNCOMMITTED級别,事務中的修改,即使沒有送出,對其他事務也都是可見的。事務可以讀取未送出的資料,這也被稱作髒讀。這個級别會導緻很多問題,從性能上來說,READ UNCOMMITTED不會比其他的級别好太多,但确缺乏其他級别的很多好處,除非真的有必要的理由,在實際應用中一般很少使用。

  2. READ COMMITED 送出讀 

    大多數資料庫系統的預設隔離級别是 READ COMMITED(但mysql不是)。READ COMMITED滿足前面提到的隔離性簡單定義:一個事務開始時,隻能看見已經送出的事務所做的修改。換句話說,一個事務從開始直到送出之前,所做的任何修改對其他事務都是不可見的。這個級别有時候也叫做不可重複讀,因為兩次執行同樣的查詢,可能會得到不一樣的結果。

  3. REPEATABLE READ 可重複寫 

    MySQL的預設事務隔離級别。 

    REPEATABLE READ解決了髒讀的問題。該級别保證了在同一個事務中多次讀取同樣的記錄的結果是一緻的。但是理論上,可重複讀隔離級别還是無法解決另一個幻讀的問題。所謂幻讀,指的是當某個事務在讀取某個範圍内的記錄時,另外一個事務又在該範圍内插入了新的記錄,當之前的事務再次讀取該範圍的記錄時,會産生幻行。InnoDB存儲引擎通過多版本并發控制(MVCC)解決了幻讀的問題。

  4. SERIALIZABLE 可串行化 

    SERIALIZABLE 是最高的隔離級别。它通過強事務串行執行,避免了前面說的幻讀的問題。簡單來說,SERIALIZABLE會在曲度的每一行資料都加上鎖,是以可能導緻大量的逾時和鎖争用的問題。實際應用中也很少用到這個隔離級别,隻有在非常需要確定資料一緻性而且可以接受沒有并發的情況下,才考慮使用該級别。

實驗

上面是摘自《高性能MySQL》一書,光這麼看,可能也了解不到多少東西,不如直接做實驗親身體驗一下,加深了解。

我們先簡單建立一個表 users,結構如下:

+------------+------------------+------+-----+-------------------+----------------+
| Field      | Type             | Null | Key | Default           | Extra          |
+------------+------------------+------+-----+-------------------+----------------+
| id         | int(11) unsigned | NO   | PRI | <null>            | auto_increment |
| name       | varchar(50)      | NO   |     |                   |                |
| created_at | datetime         | NO   |     | CURRENT_TIMESTAMP |                |
| updated_at | datetime         | NO   |     | CURRENT_TIMESTAMP |                |
| deleted_at | datetime         | YES  |     | <null>            |                |
+------------+------------------+------+-----+-------------------+----------------+
           

實作插入三條資料:

+----+-------------+---------------------+---------------------+------------+
| id | name        | created_at          | updated_at          | deleted_at |
+----+-------------+---------------------+---------------------+------------+
| 1  | coolcao2018 | 2018-05-28 12:48:43 | 2018-05-28 12:48:43 | <null>     |
| 2  | tom         | 2018-05-28 13:24:28 | 2018-05-28 13:24:28 | <null>     |
| 3  | lili        | 2018-05-31 08:54:28 | 2018-05-31 08:54:28 | <null>     |
+----+-------------+---------------------+---------------------+------------+
           

然後打開兩個終端,A和B分别表示兩個使用者同時在操作。

READ UNCOMMITTED

對于使用者A,操作:

mysql root@localhost:test> set session transaction isolation level read uncommitted;
mysql root@localhost:test> start transaction;
mysql root@localhost:test> select * from users;
+----+-------------+---------------------+---------------------+------------+
| id | name        | created_at          | updated_at          | deleted_at |
+----+-------------+---------------------+---------------------+------------+
| 1  | coolcao2018 | 2018-05-28 12:48:43 | 2018-05-28 12:48:43 | <null>     |
| 2  | tom         | 2018-05-28 13:24:28 | 2018-05-28 13:24:28 | <null>     |
| 3  | lili        | 2018-05-31 08:54:28 | 2018-05-31 08:54:28 | <null>     |
+----+-------------+---------------------+---------------------+------------+
           

然後,使用者B操作,

mysql root@localhost:test> set session transaction isolation level read uncommitted;
mysql root@localhost:test> start transaction;
mysql root@localhost:test> update users set name='coolcao' where id=1;
           

此時,使用者B并未送出事務,使用者A進行查詢操作看看:

mysql root@localhost:test> select * from users;
+----+---------+---------------------+---------------------+------------+
| id | name    | created_at          | updated_at          | deleted_at |
+----+---------+---------------------+---------------------+------------+
| 1  | coolcao | 2018-05-28 12:48:43 | 2018-05-28 12:48:43 | <null>     |
| 2  | tom     | 2018-05-28 13:24:28 | 2018-05-28 13:24:28 | <null>     |
| 3  | lili    | 2018-05-31 08:54:28 | 2018-05-31 08:54:28 | <null>     |
+----+---------+---------------------+---------------------+------------+
           

從整個過程來看,使用者B還并未送出事務,但是A卻已經能夠直接讀到B的更新。

從上面實驗結果來看,不難了解上面對于 READ UNCOMMITTED級别的描述: 在READ UNCOMMITTED級别,事務中的修改,即使沒有送出,對其他事務也都是可見的。

READ COMMITTED

同時将A,B兩個終端的事務級别設定為 read committed:

// 在A,B兩個終端都執行
set session transaction isolation level read committed;
           

對于A,我們開啟一個事務,然後更新一下資料,但并不送出事務:

mysql root@localhost:test>  set session transaction isolation level read committed;
mysql root@localhost:test> start transaction;
mysql root@localhost:test> select * from users;
+----+---------+---------------------+---------------------+------------+
| id | name    | created_at          | updated_at          | deleted_at |
+----+---------+---------------------+---------------------+------------+
| 1  | coolcao | 2018-05-28 12:48:43 | 2018-05-28 12:48:43 | <null>     |
| 2  | tom     | 2018-05-28 13:24:28 | 2018-05-28 13:24:28 | <null>     |
| 3  | lili    | 2018-05-31 08:54:28 | 2018-05-31 08:54:28 | <null>     |
+----+---------+---------------------+---------------------+------------+
mysql root@localhost:test> update users set name='coolcao222' where id=1;
mysql root@localhost:test> select * from users;
+----+------------+---------------------+---------------------+------------+
| id | name       | created_at          | updated_at          | deleted_at |
+----+------------+---------------------+---------------------+------------+
| 1  | coolcao222 | 2018-05-28 12:48:43 | 2018-05-28 12:48:43 | <null>     |
| 2  | tom        | 2018-05-28 13:24:28 | 2018-05-28 13:24:28 | <null>     |
| 3  | lili       | 2018-05-31 08:54:28 | 2018-05-31 08:54:28 | <null>     |
+----+------------+---------------------+---------------------+------------+
           

然後,在B終端,開啟另外一個事務,進行資料查詢:

mysql root@localhost:test> set session transaction isolation level read committed;
mysql root@localhost:test> start transaction;
mysql root@localhost:test> select * from users;
+----+---------+---------------------+---------------------+------------+
| id | name    | created_at          | updated_at          | deleted_at |
+----+---------+---------------------+---------------------+------------+
| 1  | coolcao | 2018-05-28 12:48:43 | 2018-05-28 12:48:43 | <null>     |
| 2  | tom     | 2018-05-28 13:24:28 | 2018-05-28 13:24:28 | <null>     |
| 3  | lili    | 2018-05-31 08:54:28 | 2018-05-31 08:54:28 | <null>     |
+----+---------+---------------------+---------------------+------------+
           

然後,将A事務送出:

commit;
           

這時,再在B查詢 :

mysql root@localhost:test> select * from users;
+----+------------+---------------------+---------------------+------------+
| id | name       | created_at          | updated_at          | deleted_at |
+----+------------+---------------------+---------------------+------------+
| 1  | coolcao222 | 2018-05-28 12:48:43 | 2018-05-28 12:48:43 | <null>     |
| 2  | tom        | 2018-05-28 13:24:28 | 2018-05-28 13:24:28 | <null>     |
| 3  | lili       | 2018-05-31 08:54:28 | 2018-05-31 08:54:28 | <null>     |
+----+------------+---------------------+---------------------+------------+
           

從結果來看,也不難了解 read committed級别,對于一個事務,隻能讀取到目前事務的資料和其他已經送出的事務的資料,對于其他未送出事務的資料,讀不到。

而且,從上面的實驗結果中,我們也看到了,會話B在會話A送出事務前後查詢的結果并不一緻,這也就是上面所說的,不可重複讀。

REPEATABLE READ 可重複讀

我們将會話A設定為REPEATABLE READ :

mysql root@localhost:test> set session transaction isolation level repeatable read;
mysql root@localhost:test> start transaction;
mysql root@localhost:test> select * from users;
+----+---------+---------------------+---------------------+------------+
| id | name    | created_at          | updated_at          | deleted_at |
+----+---------+---------------------+---------------------+------------+
| 1  | coolcao | 2018-08-10 15:21:02 | 2018-08-10 15:21:02 | <null>     |
| 2  | tom     | 2018-08-10 15:21:07 | 2018-08-10 15:21:07 | <null>     |
| 3  | lili    | 2018-08-10 15:21:11 | 2018-08-10 15:21:11 | <null>     |
+----+---------+---------------------+---------------------+------------+
           

此時,我們在B終端插入一條資料:

mysql root@localhost:test> insert into users (id,name) values (4,'coco');
mysql root@localhost:test> commit;
mysql root@localhost:test> select * from users;
+----+---------+---------------------+---------------------+------------+
| id | name    | created_at          | updated_at          | deleted_at |
+----+---------+---------------------+---------------------+------------+
| 1  | coolcao | 2018-08-10 15:21:02 | 2018-08-10 15:21:02 | <null>     |
| 2  | tom     | 2018-08-10 15:21:07 | 2018-08-10 15:21:07 | <null>     |
| 3  | lili    | 2018-08-10 15:21:11 | 2018-08-10 15:21:11 | <null>     |
| 4  | coco    | 2018-08-10 15:23:50 | 2018-08-10 15:23:50 | <null>     |
+----+---------+---------------------+---------------------+------------+
           

在終端B中,插入一條記錄,并送出,這時id=4的使用者已經被插入到資料庫。

此時,再回到終端A,查詢:

mysql root@localhost:test> select * from users;
+----+---------+---------------------+---------------------+------------+
| id | name    | created_at          | updated_at          | deleted_at |
+----+---------+---------------------+---------------------+------------+
| 1  | coolcao | 2018-08-10 15:21:02 | 2018-08-10 15:21:02 | <null>     |
| 2  | tom     | 2018-08-10 15:21:07 | 2018-08-10 15:21:07 | <null>     |
| 3  | lili    | 2018-08-10 15:21:11 | 2018-08-10 15:21:11 | <null>     |
+----+---------+---------------------+---------------------+------------+
           

哎,查詢的結果中,沒有B剛插入的id=4的使用者,這也就是說該級别的事務隔離,保證了在同一個事務中多次讀取同樣的記錄的結果是一緻的。這時,我們在A中插入一條記錄:

mysql root@localhost:test> insert into users (id,name) values (4,'coco');
(1062, u"Duplicate entry '4' for key 'PRIMARY'")
           

哎,這個時候,資料庫報錯了,提示主鍵重複。明明我在這個事務中,查詢的資料隻有1,2,3,為什麼插入4的時候提示主鍵沖突呢?是發生幻覺了麼?是的,發生“幻讀”了。由于REPEATABLE READ級别的隔離,在一個事務中,多次讀取同樣記錄的結果是一緻的,在這多次讀取之間,被别的事務插入了新的資料,這時前事務再插入資料,必然會導緻錯誤。

SERIALIZABLE 可串行化

我們将A,B同時設定為SERIALIZABLE, 然後在A開啟是個事務,做一個簡單查詢:

mysql root@localhost:test> set session transaction isolation level serializable;
mysql root@localhost:test> start transaction;
mysql root@localhost:test> select * from users where id<10;
+----+---------+---------------------+---------------------+------------+
| id | name    | created_at          | updated_at          | deleted_at |
+----+---------+---------------------+---------------------+------------+
| 1  | coolcao | 2018-08-10 15:21:02 | 2018-08-10 15:21:02 | <null>     |
| 2  | tom     | 2018-08-10 15:21:07 | 2018-08-10 15:21:07 | <null>     |
| 3  | lili    | 2018-08-10 15:21:11 | 2018-08-10 15:21:11 | <null>     |
| 4  | coco    | 2018-08-10 15:23:50 | 2018-08-10 15:23:50 | <null>     |
| 5  | juli    | 2018-08-10 15:31:32 | 2018-08-10 15:31:32 | <null>     |
+----+---------+---------------------+---------------------+------------+
           

此時,A事務并未送出,然後在B再開啟一個事務,進行插入操作:

mysql root@localhost:test> set session transaction isolation level serializable;
mysql root@localhost:test> start transaction;
mysql root@localhost:test> insert into users (id,name) values (6,'kate');
(1205, u'Lock wait timeout exceeded; try restarting transaction')
           

你會發現,哎我去,B事務被挂住了,然後過了一段時間,提示了錯誤 

(1205, u'Lock wait timeout exceeded; try restarting transaction')

 ,說等待鎖逾時。

是的,在串行化級别,會在讀取的每一行資料都加上鎖,也就是說,上面A事務在讀取時,已經加了鎖,此時B事務在插入操作時,得等待鎖的放開,時間一長,A鎖未放開,B就報錯了。

從實驗中可以看出,可串行化級别,由于要保證避免幻讀而加了鎖導緻效率以及可能會觸發的等待鎖逾時等錯誤,實際應用中,該級别的事務隔離也很少使用。

對照着實驗結果,來了解上面四個隔離級别,就容易了解了。

1、具有1-5工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加群。

2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間内進修、跳槽拿高薪的可以加群。

3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,常用設計思想,常用java開發架構掌握熟練的,可以加群。

4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加群。

5.群号 468947140,點選連結加入群聊【Java-BATJ企業級資深架構】:https://jq.qq.com/?_wv=1027&k=57VIGuh

6.阿裡Java進階大牛直播講解知識點,分享知識,知識點都是各位老師多年工作經驗的梳理和總結,帶着大家全面、科學地建立自己的技術體系和技術認知!