MySql事務基礎知識(併發問題)

MySql事務

最近看了很多Mysql事務的基礎知識,加上自己的一些理解,寫下此筆記(也是本人第一篇啊)!!!
PS:排版不太好,見諒了
事務實現原理
Innodb存儲引擎爲每行記錄都自動生成三個隱藏字段:

6字節的事務ID(DB_TRX_ID )
7字節的回滾指針(DB_ROLL_PTR)
隱藏的ID

6字節的事物ID用來標識該行所述的事務,7字節的回滾指針需要了解下Innodb的事務模型。
在這裏插入圖片描述
如圖,具體參考博文
首先要知道mysql事務是日誌先行的。
redo log
redo log就是保存執行的SQL語句到一個指定的Log文件,當Mysql執行recovery時重新執行redo log記錄的SQL操作即可。當客戶端執行每條SQL(更新語句)時,redo log會被首先寫入log buffer;當客戶端執行COMMIT命令時,log buffer中的內容會被視情況刷新到磁盤。redo log在磁盤上作爲一個獨立的文件存在,即Innodb的log文件,指data文件夾下ib_logfile文件。
undo log
與redo log相反,undo log是爲回滾而用,具體內容就是copy事務前的數據庫內容(準確的說是與事務相反的sql操作)到undo buffer,在適合的時間把undo buffer中的內容刷新到磁盤。undo buffer與redo buffer一樣,也是環形緩衝,但當緩衝滿的時候,undo buffer中的內容會也會被刷新到磁盤;與redo log不同的是,磁盤上不存在單獨的undo log文件,所有的undo log均存放在主ibd數據文件中(表空間),指data文件夾下ibdata文件,即使客戶端設置了每表一個數據文件也是如此。

日誌詳細工作流程如下:
在這裏插入圖片描述其中innodb_flush_log_at_trx_commit=0|1|2要關注。

注意 事務中sql語句執行出現異常,不會自動回滾,只會報錯,需要手動回滾。這和服務器斷電不一樣的。
另外事務要控制大小,比較小的事務好,因爲事務保證ACID犧牲了性能的,並且事務越大,出現異常就丟失的數據多,要進行事務分解。
MVCC
MVCC(Multi-Version Concurrency Control)多版本併發控制,是通過undo log來實現的,通過在每行記錄後面保存2個隱藏的列DB_TRX_ID和DB_ROLL_PTR,可以追蹤到當前事務下的版本,

MVCC機制保存了數據在某個時間點的快照。

 MVCC最大的作用是:  實現了非阻塞的讀操作,寫操作也只鎖定了必要的行。

MYSQL的MVCC 只在 read committed 和 repeatable read 2個隔離級別下工作.

在MVCC的機制下,mysql InnoDB(默認隔離級別)的增刪改查變成了如下模式:具體參考博文

SELECT:
1, InnoDB只查找版本早於當前事務版本的數據行(行的系統版本號小於等於事務的系統版本號)
2, 行的刪除號要麼未定義,要麼大於當前事務版本號,這樣可以確保事務讀取到的行,在事務開始之前未被刪除.

INSERT:
InnoDB 爲新插入的每一行保存當前系統版本號做爲行版本號。

DELETE:
INNODB 爲刪除的每一行保存當前系統版本號作爲行刪除標識。

UPDATE:
InnoDB 爲插入的每一行新記錄,保存當前系統版本號作爲行版本號,同時保存當前系統版本號到原來的行作爲行刪除標識。
注意: 上面的讀取方式只在InnoDB默認隔離級別下工作,其它的隔離級別會有很大的差異。

事務特性

1.原子性:一個事務(事務中的sql操作)要麼全部做完,要麼全部不做,不會出現只做一部分的情形,即事務是不可分割的一組操作。如A給B轉帳,不會出現A的錢少了,B的錢卻沒有增加的情況。
2.一致性: 事務的執行前後,保證數據一致性。如轉賬完成後A減少100,B一定增加100。
3. 持久性:事務一旦提交,數據被永久保存到數據庫中;
4. 隔離性:即一個事務在沒有完成數據的操作(一般修改、插入、刪除)時,即事務沒有提交時,其操作結果對其它事務是不可見的。當然這裏有個隔離級別的概念,在不同隔離級別下,這裏會有不同的表現形式。

事務可恢復性與級聯回滾

前提:存儲引擎支持事務且事務隔離級別小於Serializable;級聯回滾是mysql保證事務一致性和處理事務關係的一種處理機制。

個人對級聯回滾理解是這樣的:有兩個人(兩個事務Ti和Tj),一個 好人(Tj),一個壞人(Ti),壞人(Ti)在某個人家裏偷了錢(Ti執行讀取操作),並將其收入囊中(Ti執行寫操作),然後壞人將偷來的錢給好人花(Tj讀取了Ti寫入的數據項“偷的錢”),最後這個盜竊案被破獲了(事務在執行中出現中斷),壞人進了監獄(Ti開始回滾),好人也同樣被逮捕了(Tj也開始回滾)。可以看出,好人受到了壞人的影響(兩個事務之間產生了級聯)。因此Ti的回滾造成了Tj的回滾,如果更多事務受到Ti的影響,也會回滾。參考 博文

事務隔離級別

SQL標準定義了4類隔離級別,包括了一些具體規則(鎖機制,MVCC都參與了這些規則的實現),用來限定事務內外的哪些改變是可見的,哪些是不可見的。低級別的隔離級一般支持更高的併發處理,並擁有更低的系統開銷。
1.Read Uncommitted(讀未提交): 在Read Uncommitted策略下,數據庫遵循一級封鎖協議,只對修改數據的併發操作做限制。一個事務不能修改其他事務正在修改的數據,但可以讀取到其他事務中尚未提交的修改,這些修改如果未被提交,將會成爲髒數據,Oracle有Ver控制,不會有髒讀。

2.Read committed(讀已提交):在Read committed策略下,數據庫遵循二級封鎖協議,只允許讀取已經被提交的數據,反過來講,如果一個事務修改了某行數據且尚未提交,而第二個事務要讀取這行數據的話,那麼是不允許的。在MySql的InnoDB下,雖然這種操作不被允許,但MySQL不會阻塞住數據的查詢操作,而是會查詢出數據被修改之前的備份,返回給客戶端。MySQL的這種機制稱爲MVCC(多版本併發控制),就是說數據庫在事務併發的過程中對數據維護多個版本,使得不同的事務對不同的數據版本進行讀寫(MVCC的實現參見引用中的文章)。這樣的機制反映在應用中就是,在任何時候對數據庫查詢總是可以得到數據庫中最近提交的數據。爲被提交的髒數據被隔離起來,無法被查詢到,即防止髒讀發生。

3.Repeat Read(可重複讀): Repeat Read又比Read Committed更加嚴格一點,但仍然是在二級封鎖協議的範疇,只是讀取過程受到更多MVCC的影響。在Read Committed下,允許一個事務中多次相同查詢得到不同的結果,就是所謂的不可重複讀問題。這在一些應用中是允許的,所以oracle、SQL server上默認這一隔離級別,但MySQL沒有,它默認Repeat Read級別。在這一級別下,有賴於MVCC,同一個事務中的查詢只能查到版本號不高於當前事務版本的數據,即事務只能看到該事務開始前或者被該事物影響的數據。反過來說,這一級別下,不允許事務讀取在該事務開始後新提交的數據。即防止了不可重複讀的發生。
依靠上面的機制,已經做到了在事務內數據內容的不變,但是不能保證多次查詢得到的數據數量一致。因爲在一個事務執行的過程中別的事務完全可以執行數據插入,當插入了剛好符合查詢條件的數據時,就會引發數據查詢結果集增加,引發幻讀。還有一種情況就是,如果一個事務想插入一條數據,而另一個事務已經插入了含有相同主鍵的數據,那麼當前事務也會被阻塞,並最終執行失敗,雖然當前事務根本無法查詢到這一條數據,這也是一種幻讀。

4.Serializable(可串行化): 最強事務隔離機制Serializable,它遵循三級封鎖協議,使得所有的事務必須串行化執行,只要有事務在對錶進行查詢,那麼在此事務提交前,任何其他事務的修改都會被阻塞。這解決了一切併發問題,但會造成大量的等待、阻塞甚至死鎖,使系統性能降低,一般採用Repeat Read和數據庫鎖相結合方式來替代它

併發情況下引發的問題

談到數據庫事務的隔離性,就不能不說數據庫受併發影響產生的異常了,一般包含三個數據讀問題(髒讀,不可重複度,幻讀),兩個數據寫問題。

  • 髒讀(dirty read):A事務讀取B事務尚未提交的更改數據,並在這個數據基礎上操作。如果B事務回滾,那麼A事務讀到的數據根本不是合法的,稱爲髒讀。在oracle中,由於有version控制,不會出現髒讀。

  • 不可重複讀(unrepeatable read):A事務讀取了B事務已經提交的更改(或刪除)數據。比如A事務第一次讀取數據,然後B事務更改該數據並提交,A事務再次讀取數據,兩次讀取的數據不一樣。

  • 幻讀(phantom read):A事務讀取了或意識到了B事務已經提交的新增數據。注意和不可重複讀的區別,這裏是新增,不可重複讀是更改(或刪除)。這兩種情況對策是不一樣的,對於不可重複讀,只需要採取行級鎖防止該記錄數據被更改或刪除,然而對於幻讀必須加表級鎖,防止在這個表中新增一條數據。

  • 第一類丟失更新:A事務撤銷時,把已提交的B事務的數據覆蓋掉。

  • 第二類丟失更新:A事務提交時,把已提交的B事務的數據覆蓋掉。

    如果想模擬併發,請參考文章:數據庫多會話模擬併發,實操過後可以理解的更清楚。

數據庫在併發操作下會出現上述這些問題,要解決它就要想辦法在執行可能引發問題的操作之前將該操作阻塞住,讓它等到合適的時機再執行。那麼如何挑選合適的時機阻塞操作的執行,又如何保證在調度過程執行完成後其執行結果與串行執行操作的結果相同呢?這就要了解下三級封鎖協議了

三級封鎖協議

數據庫想要在“合適”的時機阻塞住數據庫操作,那麼首先要定義好怎麼樣的時機算是“合適”,因爲各個系統支持的業務千差萬別,對數據的實時性和有效性的要求也不同。於是數據庫理論中就提出了封鎖級別的概念,對不同的同步要求採用不同的封鎖級別。

三級封鎖協議內容如下:

一級封鎖協議:事務T在寫數據R之前必須先對其加X鎖,直到事務結束才釋放。事務結束包括正常結束(COMMIT)和非正常結束(ROLLBACK)。 一級封鎖協議保證事務T是可恢復的,並且可以防止兩個事務同時操作(增,刪,改)同一數據問題。在一級封鎖協議中,如果僅僅是讀數據不會加鎖的,所以它不能保證可重複讀和不讀“髒”數據。 
二級封鎖協議:一級封鎖協議加上事務T在讀取數據R之前必須先對其加S鎖,讀完後方可釋放S鎖。 二級封鎖協議除防止了丟失修改,還可以進一步防止讀“髒”數據。但在二級封鎖協議中,由於讀完數據後即可釋放S鎖,所以它不能保證可重複讀。 
三級封鎖協議 :一級封鎖協議加上事務T在讀取數據R之前必須先對其加S鎖,直到事務結束才釋放。 三級封鎖協議除防止了丟失修改和不讀“髒”數據外,還進一步防止了不可重複讀。

事務隔離性與數據庫異常關係(0可能出現,1不會出現)

在這裏插入圖片描述

事務相關的sql語句

查看當前會話隔離級別
     select @@tx_isolation
查看系統當前隔離級別
    select @@global.tx_isolation
設置當前會話隔離級別
    set session transaction isolation level repeatable read
設置系統當前隔離級別
    set global transaction isolation level repeatable read
查看當前會話事務是否自動提交
     show variables like '%autocommit%'
設置當前會話事務是否自動提交
       set autocommit = 0 or 1;0不自動,1自動
       具體參考文章:[autocommit = 0與start transaction區別](https://www.cnblogs.com/lhp2012/p/5315928.html)
具體操作
  start transaction;
  ***sql語句**  
  savepoint [ point_name]; 
   ***sql語句**  
  commit/rollback ; 

最後談一下快照讀
快照讀就是歷史讀,也稱爲爲一致性讀或一致性非鎖定讀,與之相對的是非一致性讀(鎖定讀or當前讀).
最常用的最基本的select 就是快照讀sql,而insert、update、delete、select …for update/lock in share mode等就是當前讀,讀最新的數據。熟悉不同事務下的快照讀,在做數據備份時挺好用的。
快照讀在不同事務級別下有不同的表現形式: READ COMMITTED 隔離級別下,每次讀取都會重新生成一個快照,所以每次快照都是最新的,也因此事務中每次SELECT也可以看到其它已commit事務所作的更改;REPEATED READ 隔離級別下,快照會在事務中第一次SELECT語句執行時生成,只有在本事務中對數據進行更改纔會更新快照,因此,只有第一次SELECT之前其它已提交事務所作的更改你可以看到,但是如果已執行了SELECT,那麼其它事務commit數據,你SELECT是看不到的。具體參考這裏

參考文章:
[1]: https://www.cnblogs.com/Chenghao-He/p/7693569.html
[2]: http://www.zsythink.net/archives/1436/
[3]: https://blog.csdn.net/hulinku/article/details/79787692

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章