MySQL中的事務

秋招告一段落,浪了很久也該總結總結了,雖然面試這一類別下絕大部分都是參考的別的博客,但本身再整理一遍總感受印象更深吧。對於參考的原文都有代表原文連接html

-----------------------------------------------------------------------------------------------面試

轉自:https://www.cnblogs.com/hebao0514/category/719525.html數據庫

1、事務的四大特性(ACID)併發

1. 原子性(atomicity):一個事務必須視爲一個不可分割的最小工做單元,整個事務中的全部操做要麼所有提交成功,要麼所有失敗回滾,對於一個事務來講,不可能只執行其中的一部分操做,這就是事務的原子性。性能

2. 一致性(consistency):數據庫老是從一個一致性的狀態轉換到另外一個一致性的狀態。
拿轉帳來講,假設用戶A和用戶B二者的錢加起來一共是5000,那麼無論A和B之間如何轉帳,轉幾回帳,事務結束後兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。atom

3. 隔離性(isolation):一個事務所作的修改在最終提交之前,對其餘事務是不可見的。
好比操做同一張表時,數據庫爲每個用戶開啓的事務,不能被其餘事務的操做所幹擾,多個併發事務之間要相互隔離。spa

4. 持久性(durability):一旦事務提交,則其所作的修改就會永久保存到數據庫中。此時即便系統崩潰,修改的數據也不會丟失。命令行

2、事務的簡單使用
1. 在使用數據庫時候須要使用事務,必須先開啓事務,開啓事務的語句具體以下:線程

start transaction

2. 事務開啓以後就能夠執行SQL語句
3. SQL語句執行成功以後,須要提交事務,提交事務的語句以下:3d

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中執行,執行結果以下:

 

參考:

http://www.javashuo.com/article/p-wszauznq-s.html

 3、髒讀

(一)、什麼是髒讀

 髒讀就是指一個事務讀取了另外一個事務未提交的數據。

(二)、髒讀的例子及避免

(1). 髒讀例子:

髒讀例子:http://www.javashuo.com/article/p-rduteysi-en.html  Read 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 隔離級別能夠避免髒讀

 4、事務隔離級別

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(可串行化)是事務的最高隔離級別,它會強制對事務進行排序,使它們彼此之間不會發生衝突,從而解決髒讀、幻讀、重複讀的問題。實際上,就是在每一個讀的數據行上加上鎖。這個級別,可能致使大量超時現象和鎖競爭,實際應用中不多使用。

5、不可重複讀

(一)、什麼是不可重複讀

不可重複讀(Non-Repeatable Read)是指事務中兩次查詢的結果不一致,緣由是在查詢的過程當中其餘事務作了更新的操做。

例如,銀行在作統計報表的時候,第一次查詢a帳戶有1000元,第二次查詢a帳戶有900元,緣由是統計期間a帳戶取出了100元,這樣致使屢次查詢中,查詢結果不一致。

不可重複讀和髒讀有點相似,可是髒讀是讀取了另外一個事務未提交的髒數據,不可重複讀是在事務內重複讀取了別的線程已提交的數據。

note:MySQL的默認事務隔離級別是:Repeatable Read(可重複讀)

(二)、不可重複讀的例子及避免

(1). 不可重複讀的例子

不可重複讀的例子:http://www.javashuo.com/article/p-vkscqcbu-ec.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

6、幻讀

 (一)、什麼是幻讀

幻讀(Phantom Read)又稱爲虛讀,是指在一個事務內兩次查詢中數據條數不一致,幻讀和不重複讀有些不一樣,一樣是在兩次查詢過程當中,不一樣的是,幻讀是因爲其餘事務作了插入記錄的操做,致使記錄數有所增長。

例如:銀行在作統計報表時統計account表中全部用戶的總金額時候,此時總共有兩個帳戶,總共金額爲2000元,這時候新增了一個用戶帳戶,而且存入1000元,這時候銀行再次統計就會發現帳戶總金額爲3000,形成了幻讀狀況

(二)、幻讀的例子及避免

(1). 幻讀的例子

幻讀的例子:http://www.javashuo.com/article/p-fvhaovcx-es.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 事務隔離級別效率低下,比較耗數據庫性能,通常不使用。

相關文章
相關標籤/搜索