Mysql 學習筆記:InnoDB 事務和 ACID 模型

1. ACID模型

事務是一種操做數據的方式,一個事務能夠是一條SQL語句,一組SQL語句或整個程序,知足如下特徵:html

  • Atomic(原子性):事務中包含的操做被看作一個邏輯單元,要麼都成功,要麼都失敗
  • Consistency(一致性):一致性指事務將數據庫從一致狀態轉變爲下一種一致的狀態。在事務開始以前和事務結束之後,數據庫的完整性約束沒有被破壞。
  • Isolation(隔離性):隔離不一樣事務,避免互相干擾,保障所見即所得
  • Durability(持久性):事務一旦提交就是永久性的。發生宕機等故障,數據庫也能恢復

2. InnoDB的實現

InnoDB和ACID模型:https://dev.mysql.com/doc/refman/5.6/en/mysql-acid.htmlmysql

2.1 redo & undo


redo log稱爲重作日誌,是物理日誌,記錄的是磁盤頁的修改操做。redo log包括兩部分:一是內存中的日誌緩衝(redo log buffer),該部分日誌是易失性的;二是磁盤上的重作日誌文件(redo log file),該部分日誌是持久的。undo log是邏輯日誌,記錄的是數據行記錄。undo log也包含兩部分:undo log buffer、undo log。算法

2.2 持久性


事務一旦提交操做成功,該事務所作的更改就不會受到電源故障、系統崩潰等問題影響。持久性一般涉及到對磁盤存儲的寫入,並具備必定數量的冗餘,以防止寫入操做期間出現電源故障或軟件崩潰。

InnoDB經過Force Log at Commit機制實現事務的持久性,即當事務提交時,必須先將該事務的全部日誌寫入到重作日誌文件進行持久化,待事務的commit操做完成纔算完成。在InnoDB存儲引擎中,由兩部分組 成,即redo log和undo log。redo log用來保證事務的持久性,undo log用來幫助事務回滾及MVCC的功能。redo log基本上都是順序寫的,在數據庫運行時不須要對redo log的文件進行讀取操做。而undo log是須要進行隨機讀寫的。sql

刷數到磁盤是在commit時發生的,有3中不一樣到策略,經過innodb_flush_log_at_trx_commit參數控制:數據庫

master thread會每秒把redo log buffer和undo log buffer刷新到磁盤中,即便沒有commit,這就是爲何即便是長事務,commit操做也很快的緣由。因此設置爲0時,實際是沒有額外操做。2比1要安全,由於1是把數據寫入用戶空間,mysql服務掛了數據就丟了,2會把數據寫入系統空間,只會在服務器宕機是發生數據丟失。緩存

雖然用戶能夠經過設置參數innodb_flush_log_at_trx_commit爲0或2來提 高事務提交的性能,可是須要牢記的是,這種設置方法喪失了事務的ACID特性。安全

2.3 隔離性


隔離性要解決的幾個問題:服務器

1.髒讀併發

事務A對緩衝池中的數據作了修改而且尚未被提交(commit),這時被另一個事務B讀取到了數據,由於查詢是優先走緩存的。

2.不可重複讀分佈式

事務A中對同一行數據屢次讀取,若是在這期間事務B對數據進行了修改,那麼事務A會讀取到提交過的數據,形成了不一致。

3.幻讀

可重複讀要求對相同數據屢次查詢結果要一致,顯然幻讀並不屬於不可重複讀,對幻讀的解決是在serializable級別中,可是InnoDB在RR級別就解決了幻讀問題,但願不要把這兩個概念搞混。

  • 事務A查詢orderid=1 and status=1的記錄,發現記錄不存在操做insert
  • 事務B插入orderid=1 and status=1的記錄,commit
  • 事務A commit
  • 數據重複插入

4.丟失更新

丟失更新體現邏輯上,事務A的更新操做會被事務B覆蓋,如:

  1. 事務A把狀態改成2,未commit
  2. 事務B把狀態改成3,commit
  3. 事務A commit,應用程序繼續執行後續操做

這時就發生了邏輯錯誤,即:當前狀態沒有改成2,不符合邏輯預期。

ANSI/ISO SQL標準定義了4中事務隔離級別

下面解讀一下RC和RR級別

2.3.1 RC 解決髒讀

RC級別主要爲了解決髒讀,即:不能讀取到未提交的數據。經過MVCC來實現,MVCC多版本併發控制指的是 「維持一個數據的多個版本,使得讀寫操做沒有衝突」 這麼一個概念。

髒讀的緣由是事務提交以前對數據的變動會更新緩衝池中的data page,select會直接查詢index page和data page,因此能查詢到。如何隔離呢?1.鎖定讀,在修改時進行讀取會被阻塞 2.非鎖定讀。爲了提高併發性能在RC和RR級別中使用的是非鎖定讀,經過讀取undo log多個版本的快照數據實現隔離。在RC中老是讀取被鎖定行的最新一份快照數據(快照讀),由於總能讀取到最新數據因此不可重複讀。

2.3.2 RR 解決不可重複讀和幻讀

RR級別主要解決2個問題:1.不可重複讀 2.幻讀

不可重複讀

可重複讀核心要實現的是在當前事務執行過程當中對相同數據的屢次查詢結果要一致,其它事務可繼續修改數據。經過讀取事務開始時的行數據版本就能實現了(當前讀),上面提到過版本是經過undo log來實現的。

幻讀

在RR中經過加鎖來解決幻讀問題:Next-Key Lock,包含Gap Lock + Record Lock。若是索引中含有惟一索引,則降級爲Record Lock,這樣只會鎖定索引自己不會出現範圍鎖。

若是插入order_id=2的也會被鎖定,緣由是由於我在建立表時沒有對order_id加索引,看下加了以後的效果:alter table record add index order_id_status (order_id,status) ;

若是不建立組合索引會分別對order_id和status作範圍鎖,這樣基本和鎖表沒什麼區別了,使用起來挺危險的。因此若是要用鎖必定要確認鎖的範圍,最好使用主鍵或組合索引來縮小鎖定範圍。

2.4 原子性

事務的原子性保障一組操做要麼成功要麼失敗,經過redo log和undo log實現。爲何要這麼作的?由於數據庫事務的原子性比操做系統的原子性狀況要複雜,存在失敗的可能,好比插入重複的主鍵就會報錯,事務不能繼續執行,須要回滾保證原子性。

以轉帳爲例:A轉帳100元給B,須要保證A和B的帳戶餘額一塊兒完成減小和增長。

2.5 一致性

提到一致性估計立馬想起來的就是分佈式系統中不一樣數據副本之間的數據一致性問題了,而ACID中的一致性是指必須使數據庫從一個一致性狀態變到另外一個一致性狀態,如何理解?

  • 知足約束:類型一致、not null、惟一值
  • 運算結果一致:a = 100;begin ... set a = a-100; ... set a = a-100; commit; a應該等於-100。

在Mysql中除了上述數據完整性約束和運算一致性以外,還存在數據一致性問題,前面不是剛提到ACID一致性不是指數據一致性嗎?這是由於在Mysql中存在多個數據副本,如:Mysql InnoDB DML操做會先寫入buffer,這樣數據就存在緩存和磁盤兩個地方,除了定時把緩存數據寫入磁盤,還使用doublewrite buffer和崩潰恢復機制減小數據丟失致使的數據不一致問題。

若是開啓了bin log還使用了內部XA事務解決bin log和redo log之間的數據一致性,這些都是Mysql中的一致性相關問題。同時還提供了鎖機制解決在併發場景下的丟失更新致使數據和預期不一致問題。

3.鎖

單靠事務並不能解決對共享資源的併發操做帶來的互相干擾,這須要經過鎖來解決。InnoDB實現了兩種標準的行級鎖:

  • 共享鎖(S Lock),容許事務讀一行數據。
  • 排他鎖(X Lock),容許事務刪除或更新一行數據。

3.1 鎖的類型

3.1.1 一致性非鎖定讀

因爲S鎖和X鎖是不兼容的,若是讀取的行當前加了X鎖,那麼讀取不會等待鎖釋放,會去讀取行快照。在InnoDB存儲引擎經過行多版本控制(multi versioning)的方式來讀取當前執行時間數據庫中行的數據的操做叫作「非鎖定讀」。非鎖定讀經過undo log來實現,若讀取的記錄被其它事務佔用,當前事務能夠經過undo讀取以前的行版本信息(須要進行還原獲得以前的數據,和rollback同樣)。

3.1.2 一致性鎖定讀

在RR隔離級別下select操做使用非鎖定讀,可是某些狀況下用戶須要顯示的對讀取加鎖來保證數據的一致性,如:併發場景下,請求1把訂單從2改成3,請求2把訂單狀態從2改成4,這樣就會出現狀態被修改了2次會形成bug。這時就須要對讀取進行鎖定,鎖定讀是對select語句加鎖:

  • select for update 對行加X鎖
  • select lock in share mode 對行加S鎖

符合咱們預期的應該只有for update

3.2 鎖的算法

  • Record Lock:單個行記錄上的鎖。經過鎖主鍵索引實現行鎖。
  • Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄自己。經過鎖非彙集索引實現範圍鎖。
  • Next-Key Lock:Gap Lock+Record Lock,鎖定一個範圍,而且鎖定記錄自己。

4. 內部XA事務

一、2完成但3沒完成,就會致使主從不一致。InnoDB經過先作PREPARE操做,接着再進行bin log和redo log的寫入,若是宕機了,等恢復後檢查uxid是否已經提交,沒提交就從新提交,至關於作了最終一致。

5. 常見問題

Q1:耗時操做爲何不能在事務中進行?(事務長時間不提交會有什麼問題?)

A1:事務使用鎖來保證併發寫操做之間的互斥,耗時操做至關於長事務,會把mysql線程阻塞,最終不可用

參考:

相關文章
相關標籤/搜索