1. 程式人生 > 其它 >上個廁所的功夫,搞懂MySQL事務隔離級別,最新Java筆試題分享

上個廁所的功夫,搞懂MySQL事務隔離級別,最新Java筆試題分享

上個廁所的功夫,搞懂MySQL事務隔離級別,最新Java筆試題分享

事務A修改了資料,但未提交,而事務B查詢了事務A修改過卻沒有提交的資料,這就是髒讀,因為事務A可能會回滾。

場景:老闆(老叔)大喊了一嗓子,但沒有指定哪個財務改。財務A大姐和財務B大哥都聽到了,但他倆不知道由誰來改,就分別進行了下方流程操作:

時間點事務A(財務大姐)事務B(財務大哥)陳哈哈
T1

(財務大姐要給我改,查到工資:3000)

Begin;

SELECT PAY from department where `NAME` = '陳哈哈';

T2UPDATE department SET ` PAY` = ` PAY` + 10000 where `NAME` = '陳哈哈';

(財務大哥看看財務大姐改沒改)

Begin;

T3(財務大姐突然發現財務大哥也在給我改)

(一查發現。哦!已經改了)(工資:13000)

SELECT PAY from department where `NAME` = '陳哈哈';

T4

(財務大哥就繼續去摸魚了)

commit;

T5

(就把事務A回滾了)

rollback;

T6???
T7???

就這樣,因為 “髒讀”就導致我每個月少1萬大洋?

  • 2. 不可重複讀(針對其他提交前後,讀取資料本身的對比)


事務A 先 查詢了工資金額,是3000塊錢,未提交 。事務B在事務A查詢完之後,修改了工資金額,變成了13000, 在事務A前提交了;如果此時事務A再查詢一次資料,就會發現錢跟上一次查詢不一致,是13000,而不是3000。這就是不可重複讀

。強調事務A對要操作的資料被別人修改了,但在不知請的情況下拿去做之前的用途。

場景同上:老闆嗷一嗓子,但沒有指定哪個財務改。財務A大姐和財務B大哥都聽到了,但他倆不知道由誰來改,就分別進行了下方流程操作:

時間點事務A(財務大姐)事務B(財務大哥)
T1

(財務大姐要給我改,查到工資:3000)

Begin;

SELECT PAY from department where `NAME` = '陳哈哈';

(財務大哥也要給我改,查到工資:3000)

Begin;

SELECT PAY from department where `NAME` = '陳哈哈';

T2(財務大姐喝了口水)

(財務大哥直接改了)

UPDATE department SET ` PAY` = ` PAY` + 10000 where `NAME` = '陳哈哈';

T3

(大姐正想改,突發強迫症,想再查一下,查到工資:13000)

SELECT PAY from department where `NAME` = '陳哈哈';

T4(大姐:???)

(財務大哥就繼續去摸魚了)

commit;

T5

(最終大姐回滾了,雖然並沒有改什麼,但你至少明白了什麼是“強迫症”)

rollback;

T6(大姐把BUG報給了產品部)
T7

(一位35歲程式設計師被祭天)

對於不可重複讀,說簡單點就是同一個事物內,查到的結果都不一致,就失去了MySQL的“一致性”,這是很嚴重的錯誤。你想,如果財務大姐沒有二次確認,而是直接以第一次查詢為準,又給我加了1萬怎麼辦?想想還有點小激動呢。

  • 3. 幻讀(針對其他提交前後,讀取資料條數的對比)


幻讀是指在同一個事務中,存在前後兩次查詢同一個範圍的資料,但是第二次查詢卻看到了第一次查詢沒看到的行,一般情況下只新增。

事務A先修改了某個表的所有紀錄的狀態欄位為已處理,未提交;事務B也在此時新增了一條未處理的記錄,並提交了;事務A隨後查詢記錄,卻發現有一條記錄是未處理的,很是詫異,剛剛不是全部修改為已處理嘛,以為出現了幻覺,這就是幻讀。

場景:老闆每個月審批一次漲薪(審批表:shenpiTable),這時財務剛剛把我的工資申請提交了,老闆正好在審批。一鍵審批通過後,突然看到了一條新的“未審批”記錄(新增的),還是大侄子陳哈哈的。

老闆:有幻覺?有BUG!!等等,我如果假裝看不到這月是不是就省了1萬塊大洋?

陳哈哈:???

時間點事務A(老闆)事務B(財務大哥)
T1

(老闆審批加工資申請,並沒有注意到“陳哈哈”)

Begin;

SELECT *from shenpiTablewhere Status = '未通過';

(財務大哥準備給我提交漲工資申請記錄)

Begin;

SELECT *from shenpiTablewhere Status = '未通過';

T2(老闆喝了口水)

(財務大哥摸了摸魚)

T3

(老闆一鍵通過了,頭都沒抬那種~)

UPDATE shenpiTableSET Status = ‘通過’ where shenpiTable= ‘未通過’;

(財務大哥摸了摸魚)

T4

(財務大哥新增一條我的申請記錄)

insert intoshenpiTable values(“xxx”,"陳哈哈",“未通過”);

T5

(老闆又確認一下,突然發現我的審批還沒通過。一臉懵逼,突然想到了什麼,果斷commit,笑嘻嘻的去洗腳了。)

SELECT *from shenpiTablewhere Status = '未通過';

commit;

commit;
T6
T7

(第二天聽到又有一位程式設計師被我打死)

  • 髒讀說的是事務知道了自己本不應該知道的東西,強調的動作是查詢,我看到了自己不該看的東西 ;

  • 不可重複讀強調的是一個人查的時候,其他人卻可以增刪改, 但我卻不知道資料被改了,還拿去做了之前的用途;

  • 幻讀強調的是我修改了資料,等我要查的時候,卻發現有我沒有修改的記錄,為什麼,因為有其他人插了一條新的。

?

隔離級別概述

==================

為了解決上述問題,MySQL制定了四種不同的“隔離級別”,包括:讀未提交(read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和序列化(serializable )。

隔離級別效果
讀未提交(RU)一個事務還沒提交時,它做的變更就能被別的事務看到。(別的事務指同一時間進行的增刪改查操作)
讀提交(RC)一個事務提交(commit)之後,它做的變更才會被其他事務看到。
可重複讀(RR)一個事務執行過程中看到的資料,總是跟這個事務在啟動時看到的資料是一致的。當然在可重複讀隔離級別下,未提交變更對其他事務也是不可見的。
序列(xíng)化(S)正如物理書上寫的,序列是單線路,顧名思義在MySQL中同一時刻只允許單個事務執行,“寫”會加“寫鎖”,“讀”會加“讀鎖”。當出現讀寫鎖衝突的時候,後訪問的事務必須等前一個事務執行完成,才能繼續執行。

例項分析

================

(場景再現)老闆:原來陳哈哈又雙叒(ruò)叕(zhuó)是我失散多年的大侄子!財務,把 “陳哈哈” 的工資漲?“10000” !

以下表中的兩個事務為例,看看在不同隔離級別下,分別會出現什麼結果。能否避免上述問題呢?

時間點(寶強綠)事務A事務B
T1

Begin;

SELECT PAY from department where `NAME` = '陳哈哈';

(查詢工資:3000)

T2Begin;
T3

SELECT PAY from department where `NAME` = '陳哈哈';

(查詢工資:3000)

T4UPDATE department SET ` PAY` = `PAY` + 10000 where `NAME` = '陳哈哈';
T5

SELECT PAY from department where `NAME` = '陳哈哈';

(查詢工資:Res_A1)

T6commit;
T7

SELECT PAY from department where `NAME` = '陳哈哈';

(查詢工資:Res_A2)

T8commit;
T9

SELECT PAY from department where `NAME` = '陳哈哈';

(查詢工資:Res_A3)

  • 讀未提交(RU):


讀未提交Res_A1Res_A2Res_A3
結果130001300013000

在RU隔離級別下,事務A 在T5時刻,就可以提前讀到未提交的事務B 結果。

  • 讀提交(RC):


讀提交Res_A1Res_A2Res_A3
結果30001300013000

讀提交又叫讀已提交,在RC隔離級別下,事務A 需要在 事務B commit提交後,才能看到事務B 修改的結果。所以在T5時刻,事務A 查到的陳哈哈的工資是 3000。

  • 可重複讀(RR)


可重複讀Res_A1Res_A2Res_A3
結果3000300013000

可重複讀是MySQL預設的隔離級別,在RR級別下,對於所有進行中(begin - commit)的事務,比如事務A,無論執行多少次SELECT(查詢表 department ),只能看到的是同一張 department 表的結果檢視(ReadView),該檢視(ReadView)是在本事務啟動(begin)時生成的,在事務A 結束(commit)後釋放。該隔離級別會保證單事務內檢視檢視的一致性,稱為“可重複讀”。

  • 序列(xíng)化(S)


序列化Res_A1Res_A2Res_A3
結果3000300013000

序列化隔離級別不支援併發事務,由於事務A 早於事務B,事務A執行SELECT時,就給 department 表加了鎖,事務B 需要等事務A 結束後才能執行,因此T5、T7時刻是 3000,T8時刻事務A提交,事務B釋放鎖並執行,最後T9時刻查到我的工資是 13000。

原理描述

====

在實現上,資料庫裡面會建立一個檢視,訪問的時候以檢視的邏輯結果為準。在MySQL預設的隔離級別“可重複讀”隔離級別下,這個檢視是在事務啟動時建立的,整個事務存在期間都用這個檢視。在“讀提交”隔離級別下,這個檢視是在每個 SQL 語句開始執行的時候建立的。這裡需要注意的是,“讀未提交”隔離級別下直接返回記錄上的最新值,沒有檢視概念;而“序列化”隔離級別下直接用加鎖的方式來避免並行訪問。

我們可以看到在不同的隔離級別下,資料庫行為是有所不同的。Oracle 資料庫的預設隔離級別其實就是“讀提交”,因此對於一些從 Oracle 遷移到 MySQL 的應用,為保證資料庫隔離級別的一致,你一定要記得將 MySQL 的隔離級別設定為“讀提交”。

配置的方式是,將啟動引數 transaction-isolation 的值設定成 READ-COMMITTED。你可以用 show variables 來檢視當前的值。


mysql> show variables like 'transaction_isolation';

?

+-----------------------+----------------+

| Variable_name | Value |

+-----------------------+----------------+

| transaction_isolation | READ-COMMITTED |

+-----------------------+----------------+

總結來說,存在即合理,每種隔離級別都有自己的使用場景,你要根據自己的業務情況來定。我想你可能會問那什麼時候需要“可重複讀”的場景呢?我們來看一個數據校對邏輯的案例。

假設你在管理一個個人銀行賬戶表。一個表存了每個月月底的餘額,一個表存了賬單明細。這時候你要做資料校對,也就是判斷上個月的餘額和當前餘額的差額,是否與本月的賬單明細一致。你一定希望在校對過程中,即使有使用者發生了一筆新的交易,也不影響你的校對結果。

這時候使用“可重複讀”隔離級別就很方便。事務啟動時的檢視可以認為是靜態的,不受其他事務更新的影響。

四種隔離級別的問題解決情況

=============

| 標題 | 髒讀 | 不可重複讀 | 幻讀 |

| --- | --- | --- | --- |

| 讀未提交(RU) | × | × | × |

| 讀提交(RC) | √ | × | × |

| 可重複讀(RR) | √ | √ | |

| 序列(xíng)化(S) | √ | √ | √ |

測試建表語句

======


-- 建表語句

DROP TABLE IF EXISTS `department`;

CREATE TABLE `department` (

  `ID` int(11) NOT NULL AUTO_INCREMENT,

  `NAME` varchar(30) CHARACTER SET utf8mb4 NOT NULL,

  `SEX` char(2) NOT NULL,

  `AGE` int(11) NOT NULL,

  `CLASS` varchar(10) NOT NULL,

  `PAY` int(11) NOT NULL,

  `HOBBY` varchar(100) DEFAULT NULL,

  PRIMARY KEY (`ID`)

) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;

INSERT INTO department (ID, NAME, SEX, AGE, CLASS, PAY, HOBBY) VALUES ('1', '陳哈哈', '男', '25', '技術1部', '3000', '摸魚');

INSERT INTO department (ID, NAME, SEX, AGE, CLASS, PAY, HOBBY) VALUES ('2', '扈亞鵬', '男', '25', '技術1部', '4000', '美食');

INSERT INTO department (ID, NAME, SEX, AGE, CLASS, PAY, HOBBY) VALUES ('3', '劉曉莉', '女', '24', '技術1部', '4000', '摸魚');

INSERT INTO department (ID, NAME, SEX, AGE, CLASS, PAY, HOBBY) VALUES ('5', '徐立楠', '女', '24', '技術1部', '4000', '閱讀');

INSERT INTO department (ID, NAME, SEX, AGE, CLASS, PAY, HOBBY) VALUES ('6', '顧昊', '男', '25', '技術1部', '4000', '摸魚');

總結

本文從基礎到高階再到實戰,由淺入深,把MySQL講的清清楚楚,明明白白,這應該是我目前為止看到過最好的有關MySQL的學習筆記了,我相信如果你把這份筆記認真看完後,無論是工作中碰到的問題還是被面試官問到的問題都能迎刃而解!

重要的事:需要領取完整版的MySQL學習筆記的話,請轉發+關注後點這裡免費獲取到免費的下載方式!

MySQL50道高頻面試題整理: