資料庫事務隔離

塵虛緣_KY發表於2016-07-10

目錄

資料庫隔離級別

隔離級別下的問題

髒讀

不可重複讀

幻讀

事務隔離的實現

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實戰詳解》 

相關文章