你們也許據說過 MySQL 的事務在高併發執行的時候可能會發生髒讀、不可重複讀、幻讀等問題。對於有處理高併發經驗的老鳥,可能認知會更深一些因此以爲 so easy~「老鳥請點紅叉離開,或者發起友好評論O(∩_∩)O哈哈~」,不過對於像我這種難以接觸到高併發業務場景的初學者來講,也就只能看幾篇博文,瞭解一下概念,紙上談兵/(ㄒoㄒ)/~~。不過本着「打破砂鍋問到底」的精神,決定經過作實驗來提升對其理解,順便加強記憶(起碼找工做被問到還能說兩句)。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
複製代碼
本次實驗採用 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 | | +-------+----------+------+-----+---------+----------------+ */
複製代碼
髒讀的概念以下:學習
事務中的修改,即便沒有提交,對其餘事務也都是可見的。事務能夠讀取未提交的數據,這也被稱做髒讀。測試
我的認爲髒讀的反作用是最大的,如今經過實驗證實髒讀的危害。
實驗users
表以下:
/* +----+------+--------+ | id | name | money | +----+------+--------+ | 1 | 小王 | 1000.0 | | 2 | 小明 | 0.0 | +----+------+--------+ */
複製代碼
實驗步驟表:
時間 | 客戶端 A | 客戶端 B |
---|---|---|
T1 | 設置事務隔離級別爲 read uncommitted | 設置事務隔離級別爲 read uncommitted |
T2 | 開始事務 Abegin; |
|
T3 | 小王轉款給小明 500 元update users set money=money-500 where id = 1; update users set money=money+500 where id = 2; |
|
T4 | 開始事務 Bbegin; |
|
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 最後沒有成功提交而是進行了回滾,這就致使了用戶餘額被扣款爲負數的災難。
不可重複讀的概念以下:
一個事務開始時,只能看見已經提交的事務所作的修改。換句話說,一個事務從開始直到提交以前,所作的任何修改對其餘事務都是不可見的。可是兩次執行一樣的查詢,可能會獲得不同的結果。
實驗users
表以下:
/* +----+------+--------+ | id | name | money | +----+------+--------+ | 1 | 小王 | 1000.0 | | 2 | 小明 | 0.0 | +----+------+--------+ */
複製代碼
實驗步驟表:
時間 | 客戶端 A | 客戶端 B |
---|---|---|
T1 | 設置事務隔離級別爲 read committed | 設置事務隔離級別爲 read committed |
T2 | 開始事務 Abegin; |
|
T3 | 查詢小明餘額select * from users where id = 2; 餘額爲 0 元 |
|
T4 | 開始事務 Bbegin; |
|
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 的查詢結果仍是同樣的。
幻讀概念以下:
所謂幻讀,指的是當某個事務在讀取某個範圍內的記錄時,另一個事務又在該範圍內插入了新的記錄,當以前的事務再次讀取該範圍的記錄時,會產生幻行。InnoDB存儲引擎經過多版本併發控制(MVCC)解決了幻讀的問題。
通過本人測試發如今 可重複讀(repeatable read)
的事務隔離級別下,MySQL 不會產生幻行可是能夠經過寫入一行數據來證實幻讀問題的存在。
實驗users
表以下:
/* +----+------+--------+ | id | name | money | +----+------+--------+ | 1 | 小王 | 1000.0 | | 2 | 小明 | 0.0 | +----+------+--------+ */
複製代碼
實驗步驟表:
時間 | 客戶端 A | 客戶端 B |
---|---|---|
T1 | 設置事務隔離級別爲 repeatable read | 設置事務隔離級別爲 repeatable read |
T2 | 開始事務 Abegin; |
|
T3 | 開始事務 Bbegin; |
|
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 的行!而在插入該行時卻出現了該行已存在的報錯……也許這就是叫幻讀的緣由吧。
網上已有不少這種類型的文章,本文也參考了許多內容,之因此還要「老調重彈」是由於「紙上得來終覺淺,絕知此事要躬行」,實踐纔是檢驗真理的惟一標準,固然本文也可能出現謬誤,歡迎指正。心裏 OS:數據庫真的後端的一塊大頭,不想成天 CRUD 就要更深刻的學啊。感受《高性能 MySQL》這本書不錯,有空要研讀一下,最後以爲頗有必要學習關於 MySQL 鎖相關的內容。