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