MYSQL---事務

本文將弄明白以下幾個問題。

一、思考

問題一:事務是什麼?爲了解決什麼問題?有哪些特性?

問題二:各種事務的隔離級別的區別?

二、分析

這篇文章先真正搞清楚事務的特性,並不分析事務的實現原理,如果想了解事務的實現原理,請移步【MYSQL---事務實現原理】。

2.1事務是什麼

概念

來自於維基百科:

數據庫事務(簡稱:事務)是數據庫管理系統執行過程中的一個邏輯單位,由一個有限的數據庫操作序列構成。

 目的

來自於維基百科:

事務包含了一個序列的對數據庫的讀寫操作。其主要目的在於:

  1. 爲數據庫操作序列提供了一個從失敗恢復到正常狀態的方法,同時提供了數據庫即使在異常狀態下也能保持一致性的方法。
  2. 在多個應用程序併發訪問數據庫時,可以在這些應用程序之間提供一個隔離方法,以防止彼此的操作相互干擾。

 2.2事務特性

事務要實現上文所介紹的目的,必然要具備一定的特性(ACID):

原子性(Atomicity)

數據庫事務作爲一個整體,其包含的所有操作,要麼全部被執行,要麼全部不執行。

一致性(Consistency)

事務應確保數據庫從一個一致性狀態轉變爲另一個一致性狀態。

隔離性(Isolation)

多個事務執行時,一個事務的執行不影響其他事務的執行。

持久性(Durability)

已提交的事務對數據庫所做的更改,將永遠保留在數據庫中。

數據庫的原子性可以通過回滾來控制(undolog)。一致性需要我們的業務邏輯支持,保證數據一致性,在數據庫層面,只要事務執行過程中不發生異常,就認爲一致。持久性通過確保對數據庫的更改一定會被持久化到數據庫文件(redolog)。這三點對於開發人員來說干涉較少,而對於隔離性,數據庫提供了四種隔離級別,供我們選擇,各有優缺點,請看下文。

2.3事務的隔離級別

事務共有四種隔離級別,在不同的隔離級別下可能會存在各種問題,比如髒讀,不可重複讀,幻讀等。

相關名詞

髒讀

假設有事務A,B和數據D。

事務A讀取了事務B修改的數據D,此時事務B還未提交,然後事務B又對修改進行了回滾或者再次修改,導致事務A讀取到的數據是髒數據,稱爲髒讀。

不可重複讀

同一個事務內兩次讀取同一行數據的結果不一致。

幻讀

同一事務內兩個相同的查詢語句得到的數據集不一致。

隔離級別設置

InnoDB提供了以下四種隔離級別:讀未提交( READ UNCOMMITTED)、讀已提交(READ COMMITTED)、可重複讀(REPEATABLE READ)、可串行化(SERIALIZABLE)。

而默認的隔離級別爲可重複讀(RR)。我們可以通過以下參數重新設置事務的隔離級別:

隔離級別比較

假設有表t,字段(id,name);事務A和B。我們模擬不同隔離級別下的場景。

  • 讀未提交( READ UNCOMMITTED)

未提交的事務數據也能被其他事務讀到,舉例

初始數據(1,‘zhangsan’),事務A和B執行順序如下:

事務A 事務B

SET autocommit=0;
START TRANSACTION;

SET autocommit=0;
START TRANSACTION;

//步驟一

UPDATE t SET name='a' WHERE id=1;    //name='a'

//步驟二(髒讀)

SELECT name FROM t WHERE id=1;  //name='a';

 

 

//步驟三

....一些其他操作

 

 

//步驟四

UPDATE t SET name='b' WHERE id=1;     // name='b'

COMMIT;

//步驟五(不可重複讀)

SELECT name FROM t WHERE id=1;  //name='b';

//步驟六

...一些其他操作

COMMIT;

 

問題

由上圖可知,由於隔離級別爲“讀未提交”,事務A在步驟二中可讀取到事務B未提交的數據,此時事務A認爲name爲a,便拿到這個值進行步驟三的其它業務邏輯操作,但是事務B在步驟四中又修改了name的值爲b,導致事務A在步驟二中得到的數據變爲髒數據,產生“髒讀”現象,步驟三種的業務邏輯就可能出錯。接下來步驟五中又讀取name的值爲b,發現和第一次讀取的值不一樣,導致步驟六可能出錯,又產生了“不可重複讀”現象。

綜上所述,讀未提交隔離級別,會產生“髒讀”和“不可重複讀”現象。

  • 讀已提交(READ COMMITTED)

已經提交的事務數據才能被其他事務讀到,舉例

初始數據(1,‘zhangsan’),事務A和B執行順序如下:

事務A 事務B

SET autocommit=0;
START TRANSACTION;

SET autocommit=0;
START TRANSACTION;

//步驟一

UPDATE t SET name='a' WHERE id=1;    //name='a'

//步驟二(無髒讀)

SELECT name FROM t WHERE id=1;  //name='zhangsan';

 

 

//步驟三

....一些其他操作

 

//步驟四

...一些其他操作

COMMIT;

//步驟五(不可重複讀)

SELECT name FROM t WHERE id=1;  //name='a';

//步驟六

...一些其他操作

COMMIT;

 

問題

由上圖可知,由於隔離級別爲“讀已提交”,故而事務A在步驟二中無法讀取到事務B未提交的數據,所以事務A認爲name爲zhangsan,這個時候已經解決了“讀未提交”中的“髒讀”。但是當事務B提交事務之後,事務A中又在步驟五查詢了name值,此時事務A讀取到name爲a,在事務A內兩次讀取到的name值不一致,導致接下來步驟六中的操作可能會出錯,這便產生了“不可重複讀”現象。

綜上所述,在“讀已提交”隔離級別下,雖然解決了“讀未提交”中的髒讀,但是“不可重複讀”現象依然存在。

  • 可重複讀(REPEATABLE READ)

只讀取當前事務數據,舉例

初始數據(1,‘zhangsan’),事務A和B執行順序如下:

事務A 事務B

SET autocommit=0;
START TRANSACTION;

SET autocommit=0;
START TRANSACTION;

//步驟一

UPDATE t SET name='a' WHERE id=1;    //name='a'

//步驟二(無髒讀)

SELECT name FROM t WHERE id=1;  //name='zhangsan';

 

//步驟三

SELECT COUNT(*) FROM t WHERE id>=1;  //count=1;

 

 

//步驟四

....一些其他操作

 

 

 

//步驟五

UPDATE t SET name='b' WHERE id=1;     // name='b'

INSERT INTO t (id,name) VALUES (2,'lisi');

COMMIT;

//步驟六(無不可重複讀)

SELECT name FROM t WHERE id=1;  //name='zhangsan';

//步驟七(幻讀)

SELECT COUNT(*) FROM t WHERE id>=1;  //count=2;

COMMIT;

 

 

 

問題

由上圖可知,由於隔離級別爲“可重複讀”,事務A無法讀取到事務B“修改”的數據,所以不存在“髒讀”和“不可重複讀的情況”,但是對於事務“B”的插入操作,事務A卻能讀到(這個爲什麼能讀到,等到我們後面博文分析事務的實現原理時再分析),此時對於事務A來說,兩次讀取的count值不一樣,如同產生了幻覺一樣,所以稱爲“幻讀”現象。

綜上所述,“可重複讀”隔離級別解決了“髒讀”、“不可重複”問題,但是存在“幻讀”現象。

上面理論是這樣,但是在博文【MYSQL---鎖】中有提到,由於InnoDB採用了“間隙鎖”,從而避免了在“可重複讀”隔離級別下的幻讀現象。即在“可重複讀”隔離級別下是不存在幻讀情況的。

  • 可串行化(SERIALIZABLE)

所有事務都按照順序執行,不存在併發情況,所以當然就不存在“髒讀”、“不可重複讀”、“幻讀”問題。

隔離級別選擇

讀完上述內容,或許你已經掌握了各種隔離級別的差異,那麼你是否想過,爲什麼要設置這麼多隔離級別,真實項目中如何選擇使用哪個隔離級別,InnoDB引擎爲什麼要把“可重複讀”作爲默認隔離級別?

帶着這個疑問,搜索瞭如下資料,覺得講的很好,大家有興趣可以仔細閱讀:https://www.cnblogs.com/rjzheng/p/10510174.html

我從中做了如下總結(如果有疑問的還是請看上文鏈接地址):

  • 爲什麼InnoDB默認採用“可重複讀”隔離級別?

由於歷史存在的主從複製的一個bug,而當時的MYSQL版本通過“讀已提交”隔離級別無法解決,所以採用“可重複讀”隔離級別。而且畢竟“可重複讀”解決了“不可重複讀”和“幻讀問題”。

  • 那真實項目中選用哪種隔離級別比較合適?

答案是“可重複讀”隔離級別(建議,具體還得視情況而定)。原因如下:

首先,對於“讀未提交”和“可串行化”我們一般不予考慮,因爲前者會造成業務邏輯混亂,後者性能太差。這裏主要討論“讀已提交”和“可重複讀”,那爲什麼推薦“讀已提交”,因爲:

  1. “可重複讀”存在“間隙鎖”,提高了死鎖發生的可能性。且由於“間隙鎖”的存在,使得併發插入操作性能下降。
  2. 在“可重複讀”隔離級別下,查詢條件未命中,會加間隙鎖;而“讀已提交”隔離級別,不加鎖。
  3. 在“讀已提交”隔離級別下,半一致性讀增加了update操作併發性。

那“讀已提交”情況下的“不可重複讀”和“幻讀”問題,怎麼辦?

答案是既然已經提交的數據看到無所謂,但是需要我們業務邏輯的實現自行處理這種情況,否則可能出現邏輯錯誤。出於性能考慮還是“讀已提交”更好。

三、其他 

如何查詢正在執行的事務?

通過語句:SELECT * FROM information_schema.INNODB_TRX;

可重複讀和讀已提交差異的底層原理

請看博文:https://mp.csdn.net/postedit/102211569

 

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

至此,事務的基本內容已講完,如果不對之處,還請指出探討。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------參考博文:

  1. 互聯網項目中MYSQL應該選用什麼隔離級別:https://www.cnblogs.com/rjzheng/p/10510174.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章