MySQL中的事务
秋招告一段落,浪了好久也該總結總結了,雖然面試這一類別下絕大部分都是參考的別的博客,但自己再整理一遍總感覺印象更深吧。對于參考的原文都有表明原文鏈接
-----------------------------------------------------------------------------------------------
轉自:https://www.cnblogs.com/hebao0514/category/719525.html
一、事務的四大特性(ACID)
1. 原子性(atomicity):一個事務必須視為一個不可分割的最小工作單元,整個事務中的所有操作要么全部提交成功,要么全部失敗回滾,對于一個事務來說,不可能只執行其中的一部分操作,這就是事務的原子性。
2. 一致性(consistency):數據庫總是從一個一致性的狀態轉換到另一個一致性的狀態。
拿轉賬來說,假設用戶A和用戶B兩者的錢加起來一共是5000,那么不管A和B之間如何轉賬,轉幾次賬,事務結束后兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。
3. 隔離性(isolation):一個事務所做的修改在最終提交以前,對其他事務是不可見的。
比如操作同一張表時,數據庫為每一個用戶開啟的事務,不能被其他事務的操作所干擾,多個并發事務之間要相互隔離。
4. 持久性(durability):一旦事務提交,則其所做的修改就會永久保存到數據庫中。此時即使系統崩潰,修改的數據也不會丟失。
二、事務的簡單使用
1. 在使用數據庫時候需要使用事務,必須先開啟事務,開啟事務的語句具體如下:
start transaction;
2. 事務開啟之后就可以執行SQL語句
3. SQL語句執行成功之后,需要提交事務,提交事務的語句如下:
commit;
note:
在MySQL中直接書寫的SQL語句都是自動提交的,而事務中的操作語句需要使用commit語句手動提交,只有事務提交后其中的操作才會生效。
如果不想提交事務,我們還可以使用相關語句取消事務(也稱回滾),具體語句如下:
rollback;
需要注意的是,rollback語句只能針對未提交的事務執行的回滾操作,已經提交的事務是不能回滾的。
例子:通過一個轉賬的案例演示如何使用事務。
1. 創建表
create table account(
id int primary key auto_increment,
name varchar(40),
money float
);
insert into account(name,money) values('a',1000);
insert into account(name,money) values('b',1000);
2. 使用事務來實現轉賬:首先開啟一個事務,然后通過update語句將 a賬戶的100元 轉給 b 賬戶,然后提交事務
start transaction; update account set money=money-100 where name='a'; update account set money=money+100 where name='b'; commit;
1). 在命令行中執行,執行效果如下:
事務提交和事務回滾的具體例子參考這里:事務提交、事務回滾
2). 在Navicat中執行,執行結果如下:
參考:
https://www.cnblogs.com/hebao0514/p/5490698.html
三、臟讀
(一)、什么是臟讀
臟讀就是指一個事務讀取了另一個事務未提交的數據。
(二)、臟讀的例子及避免
(1). 臟讀例子:
臟讀例子:https://www.cnblogs.com/hebao0514/p/5490764.htmlRead Uncommitted(讀未提交)(個人理解:一個事務讀到另一個事務還未提交的數據)
例子說明:開啟兩個線程,分別模擬a賬戶和b賬戶,
MySQL的默認隔離級別是Repeatable Read(可重復讀),該級別是可以避免臟讀的,因此需要將b賬戶中事務的隔離級別設置為Read Uncommitted(讀未提交)。
在a賬戶中開啟一個事務,執行轉賬功能,但未提交(commit);在b賬戶中開啟一個事務,執行查詢功能,因為b賬戶的事務隔離級別低,
就讀到了a賬戶還沒提交的數據(即出現臟讀),這時候,b誤以為a賬戶已經轉賬成功,便會給a發貨,當b發貨之后,a如果不提交事務而將事務回滾,b就會受到損失。
(2). 避免臟讀:Read Committed 隔離級別來避免臟讀的例子:
1. a賬戶(左)和b賬戶(右)當前余額:
2. 開啟事務,轉賬給b賬戶:
3. 此時a賬戶的事務并未提交,此時b賬戶查看余額:
可以看出,b賬戶中仍為1000,沒有讀到a賬戶中沒有提交的信息。說明Read Committed 隔離級別可以避免臟讀
四、事務隔離級別
https://www.cnblogs.com/hebao0514/p/5492108.html
1). Read Uncommitted 2). Read Committed 3). Repeatable Read 4). Serializable
(1)Read Uncommitted
Read UnCommitted(讀未提交)是事務中最低的級別,該級別下的事務可以讀取到另一個事務中未提交的數據,也被稱為臟讀(Dirty Read),這是相當危險的。由于該級別較低,在實際開發中避免不了任何情況,所以一般很少使用。
(2)Read Committed
大多數的數據庫管理系統的默認隔離級別都是Read Committed(讀提交),該級別下的事務只能讀取其他事務中已經提交的內容,可以避免臟讀,但是不能避免重復讀和幻讀的情況。
重復讀:在事務內讀取了別的線程已經提交的數據,但是兩次查詢讀取結果不一樣,原因是查詢的過程中其他事務做了更新操作
幻讀:在事務內兩次查詢的數據條數不一樣,原因是查詢的過程中其他事務做了添加操作
(3)Repeatable Read
Repeatable Read(可重復讀)是MySQL默認的事務隔離級別,它可以避免臟讀、不可重復讀的問題,確保同一個事務的多個實例在并發操作數據的時候,會看到相同的數據行。但是理論上,該級別會出現幻讀情況,不過MySQL的存儲引擎通過多版本并發控制機制解決了該問題,因此該級別是可以避免幻讀的。
(4)Serializable
Serializable(可串行化)是事務的最高隔離級別,它會強制對事務進行排序,使它們彼此之間不會發生沖突,從而解決臟讀、幻讀、重復讀的問題。實際上,就是在每個讀的數據行上加上鎖。這個級別,可能導致大量超時現象和鎖競爭,實際應用中很少使用。
五、不可重復讀
(一)、什么是不可重復讀
不可重復讀(Non-Repeatable Read)是指事務中兩次查詢的結果不一致,原因是在查詢的過程中其他事務做了更新的操作。
例如,銀行在做統計報表的時候,第一次查詢a賬戶有1000元,第二次查詢a賬戶有900元,原因是統計期間a賬戶取出了100元,這樣導致多次查詢中,查詢結果不一致。
不可重復讀和臟讀有點類似,但是臟讀是讀取了另一個事務未提交的臟數據,不可重復讀是在事務內重復讀取了別的線程已提交的數據。
note:MySQL的默認事務隔離級別是:Repeatable Read(可重復讀)
(二)、不可重復讀的例子及避免
(1). 不可重復讀的例子
不可重復讀的例子:https://www.cnblogs.com/hebao0514/p/5494442.html Repeatable Read(可重復讀)(個人理解:一個事務中,重復查詢的結果是一樣的,哪怕在第一次查詢之后,數據庫已經變了,查詢結果仍與第一次查詢結果一致,而不是查詢數據庫中的最新數據)
1. 線程2:查看線程2中事務隔離級別:
1. 線程2:首先在線程2中開啟一個事務,然后在當前事務中查詢各個賬戶的余額信息:
3. 線程1:線程1中不用開啟事務,直接使用update更新a賬戶,并查詢余額:
note:由于線程1只需要執行修改的操作,不需要保證同步性,因此直接執行SQL語句就可以
4. 線程2:當線程1更新操作執行完成后,在線程2中再次查詢各賬戶余額,發現a賬戶變為900:
線程2中,a賬戶兩次的查詢結果不一致,實際上這種操作是沒有錯的(雖然沒錯,但應該避免這種情況?)
例子說明:開啟兩個線程1和2,線程2的隔離級別為Read Committed。在線程2中開啟事務,查詢a賬戶為1000,此時還不提交事務;在線程1中不用開啟事務,更新a賬務為900;返回線程2(此時事務還沒有提交),查詢a賬戶變為900。即:線程2在同一個事務中,兩次查詢結果不一致。(博客例子中的a賬戶和b賬戶應該為表述錯誤,應為:線程1和線程2,操作的賬戶都是a賬戶。另外博客的線程2(即b賬戶中)繼前一篇博客的操作,已經改成了Read Committed 隔離級別)
(2).避免不可重復讀:Repeatable Read隔離級別來避免不可重復讀的例子:
1. 查看線程1中事務隔離級別:
2. 在線程1中開啟一個事務,然后在當前事務中查詢各個賬戶的余額信息:
3. 線程2中不用開啟事務,直接使用update語句執行更新操作,并查詢余額:
4. 返回線程1:當線程2更新操作執行完成后,在線程1中再次查詢賬戶余額,發現a賬戶仍未1000:
5. 如果此時在線程1中修改a賬戶,會報錯:ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
六、幻讀
(一)、什么是幻讀
幻讀(Phantom Read)又稱為虛讀,是指在一個事務內兩次查詢中數據條數不一致,幻讀和不重復讀有些不同,同樣是在兩次查詢過程中,不同的是,幻讀是由于其他事務做了插入記錄的操作,導致記錄數有所增加。
例如:銀行在做統計報表時統計account表中所有用戶的總金額時候,此時總共有兩個賬戶,總共金額為2000元,這時候新增了一個用戶賬戶,并且存入1000元,這時候銀行再次統計就會發現賬戶總金額為3000,造成了幻讀情況
(二)、幻讀的例子及避免
(1). 幻讀的例子
幻讀的例子:https://www.cnblogs.com/hebao0514/p/5494588.html Phantom Read(幻讀、虛讀)(個人理解:一個事務中,重復查詢的結果是一樣的,哪怕在第一次查詢之后,數據庫已經變了,查詢結果仍與第一次查詢結果一致,而不是查詢數據庫中的最新數據。幻讀是針對insert操作)
1. 線程2:首先設置線程2的隔離級別為Read Committed,并查詢。(可重復讀隔離級別是可以避免幻讀的出現,因此需要將事務的隔離級別設置為更低)
2. 線程2:開啟一個事務,然后在當前事務中查詢賬戶的余額信息:
3. 線程1:先查詢account表中的信息,然后進行添加操作:(線程1不用開啟事務,直接執行添加操作即可)
4. 線程2:線程1添加完記錄后,在線程2中查詢余額信息:
可以發現,在Read Committed隔離級別下,線程2中第二次查詢數據比第一查詢數據的時候多一條記錄,這種情況并不是錯誤的,但可能不符合實際需求。
例子說明:開啟兩個線程1和2,線程2的隔離級別為Read Committed。在線程2中開啟事務,查詢account表的結果為共有兩條記錄;在線程1中不用開啟事務,在線程1中添加一條記錄(c,1000);返回線程2,查詢account表,查詢結果變為共有三條記錄。即:線程2在同一個事務中,兩次查詢結果不一致。
幻讀和不重復讀的區別:
同樣在兩次查詢過程中,不同的是,幻讀是由于其他事務做了插入操作(insert),導致記錄數有所增加。而不重復讀是由于其他事務做了更新操作(update),導致同一條記錄的查詢結果不同。
(2).避免幻讀:Repeatable Read隔離級別來避免幻讀的例子:
先刪除剛才添加的(c,1000)這一條記錄
1. 線程2:為了防止出現幻讀,可以將線程2的隔離級別設置為Repeatable Read
2. 線程2:開啟一個事務,然后在當前事務中查詢賬戶的余額信息:
3. 線程1:先查詢account表中的信息,然后進行添加操作:(線程1不用開啟事務,直接執行添加操作即可)
4. 線程2:線程1添加完記錄后,在線程2中查詢余額信息
可以發現,在Repeatable Read隔離級別下,線程2中兩次查詢結果是一樣的。
5. 線程2:使用commit提交當前事務,再查詢account表,查詢到三條記錄
Repeatable Read從理論的角度是會出現幻讀的,但是MySQL內部通過多版本控制機制【實際上就是對讀取到的數據加鎖】解決這個問題。
因此,用戶才可以放心大膽使用Repeatable Read這個事務隔離級別。
note:Serializable 和 Repeatable Read都可以防止幻讀。但是Serializable 事務隔離級別效率低下,比較耗數據庫性能,一般不使用。
總結
- 上一篇: 数理方程:三类常见齐次方程及其通解
- 下一篇: 【IT之家评测室】摩尔线程 MTT S8