MySQL 事務隔離實驗-認識:髒讀、不可重複讀、幻讀

0x00 前言

你們也許據說過 MySQL 的事務在高併發執行的時候可能會發生髒讀不可重複讀幻讀等問題。對於有處理高併發經驗的老鳥,可能認知會更深一些因此以爲 so easy~「老鳥請點紅叉離開,或者發起友好評論O(∩_∩)O哈哈~」,不過對於像我這種難以接觸到高併發業務場景的初學者來講,也就只能看幾篇博文,瞭解一下概念,紙上談兵/(ㄒoㄒ)/~~。不過本着「打破砂鍋問到底」的精神,決定經過作實驗來提升對其理解,順便加強記憶(起碼找工做被問到還能說兩句)。mysql

0x01 MySql 事務隔離級別

MySql 事務隔離級別和容許併發反作用,分別以下表:sql

事務隔離級別 髒讀 不可重複讀 幻讀
讀未提交(read uncommitted)
不可重複讀(read committed)
可重複讀(repeatable read)
串行化(serializable)

由上表可知,MySQL 共支持四種事務隔離級別。表由上到下容許併發反作用愈來愈弱,彷佛咱們只要選擇串行化(serializable)的事務隔離級別就不會發生髒讀不可重複讀幻讀等問題了,可是選擇串行化(serializable)卻會帶來必定的性能降低。因此關於如何選擇事務隔離級別咱們須要對髒讀不可重複讀幻讀有必定認知,並肯定這幾種反作用對應用的影響,而後選擇合適的隔離級別。數據庫

MySQL 的默認事務隔離級別爲 可重複讀(repeatable read) 因此咱們不用擔憂「髒讀」和「不可重複讀」。後端

查詢 MySQL 事務隔離級別的語句以下:session

select @@tx_isolation;
/* 輸出結果: +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+ */
複製代碼

設置事務隔離級別:併發

-- 設置事務隔離級別爲 read committed,僅在本次會話中生效
set session transaction isolation level read committed;
複製代碼

或者能夠修改 my.cnf 配置文件使其永久生效。高併發

[mysqld]
transaction-isolation = REPEATABLE-READ
複製代碼

0x02 實驗環境

本次實驗採用 MySql 5.7.21 版本(儲存引擎爲 Innodb),測試數據表結構以下:性能

/* +-------+----------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+----------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | <null> | auto_increment | | name | char(20) | NO | | <null> | | | money | float | NO | | 0 | | +-------+----------+------+-----+---------+----------------+ */
複製代碼

0x03 髒讀

髒讀的概念以下:學習

事務中的修改,即便沒有提交,對其餘事務也都是可見的。事務能夠讀取未提交的數據,這也被稱做髒讀。測試

我的認爲髒讀的反作用是最大的,如今經過實驗證實髒讀的危害。

實驗users表以下:

/* +----+------+--------+ | id | name | money | +----+------+--------+ | 1 | 小王 | 1000.0 | | 2 | 小明 | 0.0 | +----+------+--------+ */
複製代碼

實驗步驟表:

時間 客戶端 A 客戶端 B
T1 設置事務隔離級別爲 read uncommitted 設置事務隔離級別爲 read uncommitted
T2 開始事務 A
begin;
T3 小王轉款給小明 500 元
update users set money=money-500 where id = 1;
update users set money=money+500 where id = 2;
T4 開始事務 B
begin;
T5 查詢小明帳戶餘額
select * from users where id = 2;
查詢結果爲 500 元,餘額充足則執行支付邏輯
T6 小明帳戶扣款 100 元
update users set money=money-100 where id = 2;
本條語句將會阻塞
T7 事務 A 回滾
rollback;
語句執行完畢
T8 事務 B 提交
commit;

最後咱們查詢users表,結果以下:

/* +----+------+--------+ | id | name | money | +----+------+--------+ | 1 | 小王 | 1000.0 | | 2 | 小明 | -100.0 | +----+------+--------+ */
複製代碼

使人驚訝的結果,小明的餘額變成了 -100 元!這就是髒讀的危害,咱們重點看上表的 T5,發如今事務 A 還未提交之時事務 B 便已經讀取到了事務 A 更新後的結果,這直接致使了咱們程序判斷餘額充足從而執行了扣款的邏輯。若是事務 A 成功提交那麼程序結果就是正確的,可是事務 A 最後沒有成功提交而是進行了回滾,這就致使了用戶餘額被扣款爲負數的災難。

0x04 不可重複讀

不可重複讀的概念以下:

一個事務開始時,只能看見已經提交的事務所作的修改。換句話說,一個事務從開始直到提交以前,所作的任何修改對其餘事務都是不可見的。可是兩次執行一樣的查詢,可能會獲得不同的結果。

實驗users表以下:

/* +----+------+--------+ | id | name | money | +----+------+--------+ | 1 | 小王 | 1000.0 | | 2 | 小明 | 0.0 | +----+------+--------+ */
複製代碼

實驗步驟表:

時間 客戶端 A 客戶端 B
T1 設置事務隔離級別爲 read committed 設置事務隔離級別爲 read committed
T2 開始事務 A
begin;
T3 查詢小明餘額
select * from users where id = 2;
餘額爲 0 元
T4 開始事務 B
begin;
T5 小明帳戶充值100元
update users set money=money+100 where id = 2;
T6 事務 B 提交
commit;
T7 查詢小明餘額
select * from users where id = 2;
餘額爲 100 元
T8 事務 A 提交
commit;

不可重複讀表如今於在同一個事務之中,兩個相同的查詢獲得的查詢結果卻不一樣。這是因爲兩個查詢結果之間,出現另一個事務修改了包含以前查詢結果的記錄,致使第二次查詢與第一次查詢結果不一樣。它與髒讀的區別在於修改記錄的事務 B 必須提交成功,查詢事務 A 才能讀取到修改後的記錄,若是事務 B 回滾了,事務 A 的查詢結果仍是同樣的。

0x05 幻讀

幻讀概念以下:

所謂幻讀,指的是當某個事務在讀取某個範圍內的記錄時,另一個事務又在該範圍內插入了新的記錄,當以前的事務再次讀取該範圍的記錄時,會產生幻行。InnoDB存儲引擎經過多版本併發控制(MVCC)解決了幻讀的問題。

通過本人測試發如今 可重複讀(repeatable read)的事務隔離級別下,MySQL 不會產生幻行可是能夠經過寫入一行數據來證實幻讀問題的存在。

實驗users表以下:

/* +----+------+--------+ | id | name | money | +----+------+--------+ | 1 | 小王 | 1000.0 | | 2 | 小明 | 0.0 | +----+------+--------+ */
複製代碼

實驗步驟表:

時間 客戶端 A 客戶端 B
T1 設置事務隔離級別爲 repeatable read 設置事務隔離級別爲 repeatable read
T2 開始事務 A
begin;
T3 開始事務 B
begin;
T4 插入一行
insert into users(id, name, money) values (3, "小紅",1000);
T5 事務 B 提交
commit;
T6 查詢users
select * from users;
並沒有 id 爲 3 的記錄
T7 插入一行
insert into users(id, name, money) values (3, "小紅",1000);
T8 出現報錯:(1062, u"Duplicate entry '3' for key 'PRIMARY'")

對於事務 A 來講出現的報錯就像見鬼了同樣,由於事務 A 在查詢 users 表的結果並不存在 id 爲 3 的行!而在插入該行時卻出現了該行已存在的報錯……也許這就是叫幻讀的緣由吧。

0x06 總結

網上已有不少這種類型的文章,本文也參考了許多內容,之因此還要「老調重彈」是由於「紙上得來終覺淺,絕知此事要躬行」,實踐纔是檢驗真理的惟一標準,固然本文也可能出現謬誤,歡迎指正。心裏 OS:數據庫真的後端的一塊大頭,不想成天 CRUD 就要更深刻的學啊。感受《高性能 MySQL》這本書不錯,有空要研讀一下,最後以爲頗有必要學習關於 MySQL 鎖相關的內容。

相關文章
相關標籤/搜索