數據庫事務隔離

目錄

數據庫隔離級別

隔離級別下的問題

髒讀

不可重複讀

幻讀

事務隔離的實現

MVCC

事務的啓動方式

思考及解答


事務:要麼什麼都做,要麼都不做,沒有中間狀態。 All or Nothing
     MySql是一個支持多引擎的系統,但是並不是所有的引擎都支持事務。比如MyISAM引擎就不支持事務,這也是MyISAM被InnoDB取代的重要原因之一。

    一說到事務,首先出現在我們腦海裏的是:ACID(Atomicity,Consistency,Isolation.Durability)原子性,一致性,隔離性,持久性。今天主要回憶一下隔離性

數據庫隔離級別

  • 讀未提交(read uncommit):一個事物還未提交,它做的變更就能被別的事務看到,直接返回內存記錄的最新值;
  • 讀提交(read commit):一個事務提交後,它做的變更纔會被其他事務看到;視圖是在執行的時候創建的(oracle默認);
  • 可重複讀(repeatable read):一個事務在執行過程中看到的數據,總是跟這個事務在啓動時看到的數據是一致的。未提交變更對其也是不可見的。主要用數據庫的視圖來實現,在事務啓動的時候創建的,該視圖是靜態的,不受其他事務的影響;(Mysql默認的級別)。
  • 串行化(serializable):讀和寫都會加鎖,當有衝突的時候,事務必須列隊等待執行,通過加鎖來避免並行訪問;

下面通過一個具體的例子來了解四種隔離級別對取值的影響:

事務a 事務b

啓動事務

查詢得到值 1

啓動事務
  查詢得到值 1;
  將1改爲 2;
查詢得到值v1  
  提交事務b
查詢得到值v2  
提交事務  
查詢得到值v3  

讀未提交隔離:事務a讀取的值v1是: 2。雖然事務b未提交,但是對事務a已經可見了。v2和v3的值也都是2;
讀已提交:v1的值是1。v2和v3的值是2;只有事務b提交後新值才能被事務a看到;
可重複讀:v1和v2的值是1,v3的值是2;事務在啓動到結束期間的數據必須保持一致,通過mvcc和視圖來實現;
串行化:如果是事務b先啓動,那麼在執行期間,會加鎖,事務a需要等到事務b執行完後纔可以執行。

隔離級別下的問題

髒讀

對於讀未提交隔離級別出現的髒讀:

事務a 事務b

啓動事務

查詢得到值x=1

啓動事務

 

  查詢得到值x=1
  update值x+1=2;
查詢得到值x=2  
  回滾事務
業務處理  
提交事務    

如上圖所示,如果事務b在更新完x的值後又回滾了,此時事務a便發生了髒讀x=2,無效數據

不可重複讀

讀已提交出現的不可重複讀問題:

事務a 事務b

啓動事務

查詢得到值x=1

啓動事務

 

  查詢得到值x=1
  update值x+1=2;
  事務提交
查詢得到x=2  
業務處理  
提交事務    

  對於不可重複讀和髒讀的區別也比較明顯:不可重複讀主要是因爲事務b執行的比較快,在事務a之前提交了update,但是事務a再次讀取x的值,前後的數據不一致。雖然不一致,但是數據有效。有沒有問題需要根據具體的情況而定。

幻讀

  可重複讀由於併發情況下“新插入的行”的影響,則會出現幻讀。幻讀主要的影響是:
   (1) 它破壞了加鎖的語義;
   (2) 導致了數據一致性的問題,
這個一致性主要是因爲binlog和數據庫裏的數據的不一致造成的。
爲了解決幻讀:這裏主要採用了next-key lock【行鎖和間隙鎖合稱爲next-key lock】 和gap lock[間隙鎖]來解決這個問題。但是間隙鎖也會有新的問題就是“死鎖”問題

事務的隔離級別問題及小結

  級別 問題 解決辦法
事務隔離 讀未提交 髒讀 加鎖
讀已提交 不可重複讀-幻讀 mvcc版本控制
可重複讀 gap的容易產生的死鎖 +幻讀 gap lock
串行化 無衝突,但是性能低 加讀/寫鎖

我們可以通過如下命令來查看當前數據庫的隔離級別:

show variables like "tr_isolation"  //5.6或更早的版本,8.0直接把tx_isolation刪掉
show variables like "transaction_isolation" //5.7版本以後
圖一:mysql數據庫隔離級別

事務隔離的實現

在實現上,數據庫裏面會創建一個視圖,訪問的時候以視圖的邏輯結果爲準。以“可重複讀”爲例:

圖二:多版本併發控制mvcc

     在mysql中,每條記錄在更新的時候都會同時記錄一條回滾操作undo log。通過回滾操作,我們可以得到最新值的前一個狀態。例如1被順序改爲了2-3-4,在回滾日誌中,我們都會記錄這樣一個操作。當前值是1,但是在查詢的時候每個事務會有自己的視圖,read-view。圖2中,變量x的值在A-B-C三個視圖中的值分別爲1-2-4。對於read-view C,如果事務A-B都commit了,此時想要讀取x=1的值,就必須通過執行所有的回滾日誌才能得到x=1的值,且A和B的視圖也不會刪除,因爲有比A-B更早創建的視圖C依賴它們,引來的問題就是佔用內存,尤其是長事務這就是數據庫中多版本併發控制MVCC(Multi-Version Concurrency Control )。

讀未提交

直接返回內存記錄 [InnoDB 的buffer pool]的最新值,沒有視圖的概念;

讀已提交 視圖是在執行時創建的;
可重複讀 視圖是在事務開啓時創建的[事務在啓動的時候打一個快照,別人修改的我不care];
串行化 通過加讀/寫鎖來避免並行化;

MVCC

可以通過transaction_isolation參數來設置隔離級別;
在mysql中,實際上每條記錄在更新的時候都會記錄一條回滾操作undo log 和嚴格遞增的事務id。記錄上最新值,通過回滾操作,可以得到前一個狀態的值,這也就是多版本併發控制MVCC。

事務的啓動方式

1、顯式的啓動事務:begin或start transaction。
   配套的提交語句是commit,回滾語句是rollback。主要通過redo log和undo log來實現。
2、set autocommit
    set autocommit=0,這個命令會將這個線程的自動提交事務關閉掉。只有主動的去調用commit或rollback時,纔會提交,可能會導致長事務。

ps:建議使用set autocommit=1來顯示的啓動事務;

思考及解答

問題一:mvcc的回滾日誌什麼時候刪除?
    答:當沒有事務需要用到這些回滾日誌的時候回滾日誌就會刪除。當系統裏沒有比這個回滾日誌更早的read-view的時候就可以刪除了。如何理解呢? 參考圖2。
    簡單的說:一個查詢事務開啓以後,在這個時刻之後,事務提交/回滾之前,所有的更新產生的undo log都不能被刪除。

問題二:使用長事務的弊病,爲什麼長事務會拖垮數據庫?怎麼查詢各個表中的長事務?如何避免長事務?
答:如果我們在訪問數據庫的過程中存在長事務[執行完sql語句,一直未斷開連接],那麼系統裏會存在許多很老的事務視圖,事務提交前,這些回滾記錄都必須保留,這樣就佔用了大量的內存。另外長事務還佔用鎖的資源,造成請求擠壓,吞吐量下降,可能拖垮整個數據庫。

爲什麼要避免長事務
    某些後臺應用經常需要頻繁的操作DB,爲了保證數據出錯時能回滾數據,通常都會使用事務。在使用事務的時候,儘量避免使用長事務,比如說:某個業務操作需要批量插入數據,而且數據量還不少,如果這整個操作都包在一個事務裏面,只有等到數據操作完了,DB連接纔會被釋放,一旦外部系統發起請求,併發調用這個操作,那麼一下子將有大量的DB連接被持有而沒有被釋放掉,這個時候,如果還有其他請求到來,就很大可能獲取不到數據庫連接,請求也就只能等待,整個系統的吞吐量就大大下降。

       所以寧願將事務的範圍縮小,快速操作完數據後,立刻把DB連接還給連接池,這樣後續的請求就可以拿到連接。 
       當然這樣對DB的壓力也會增加,但是總比db連接被耗光來的好。

問題三: 如何避免長事務?   

答:從應用和數據庫端兩個方面來分析:
應用端:
1、使用set autocommit=1;顯示語句來啓動事務,並且讓事務自動提交。避免長連接導致的意外長事務;
2、確定是否有不必要的只讀事務。比如好幾個select語句,其實沒有必要使用事務,這樣的可以去掉。
3、使用set MAX_EXECUTION_TIME=3000 來控制每個語句的執行最長時間,避免單個語句執行時間太長,出現長事務;
從數據庫端:
1、監控information_schema.InnoDB_trx表,設置長事務閾值,超過就報警/記錄/kill掉;
2、測試階段可以打開general_log,分析日誌行爲提前發線問題;

監控長事務的命令:

select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>6

學習筆記,內容簡單,用於複習,原內容2月有更新。
##參考資料,《MySql實戰詳解》 

發佈了250 篇原創文章 · 獲贊 191 · 訪問量 57萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章