事務定義 事務是訪問並更新資料庫中各個數據項的一個程式執行單元。在事務操作中,要不都做修改,要麼都不做。 事務特性 事務具有ACID四個特性,分別是:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)。 原子性 原子性是指要麼 ...
事務定義
事務是訪問並更新資料庫中各個數據項的一個程式執行單元。在事務操作中,要不都做修改,要麼都不做。
事務特性
事務具有ACID四個特性,分別是:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)。
原子性
原子性是指要麼不做,要麼都做。
原子性是指資料庫中不可分割的工作單位,只有使事務中所有的資料庫操作都執行成功,才算整個事務成功。
事務中任何一個SQL語句執行失敗,已經執行成功的SQL語句必須撤銷,資料庫狀態應該退回到執行事務前的狀態。
一致性
是指資料庫從一種狀態轉變為下一種一致的狀態,具體來說就是在事務開始前和事務結束以後,資料庫的完整性約束沒有被破壞。
持久性
事務處理結束後,對數據的修改就是永久的,即便系統故障也不會丟失。
持久性實現
- 重做日誌
與原子性一樣,事務的持久性也是通過日誌來實現的,MySQL 使用重做日誌(redo log)實現事務的持久性,重做日誌由兩部分組成,一是記憶體中的重做日誌緩衝區,因為重做日誌緩衝區在記憶體中,所以它是易失的,另一個就是在磁碟上的重做日誌文件,它是持久的。
當我們在一個事務中嘗試對數據進行修改時,它會先將數據從磁碟讀入記憶體,並更新記憶體中緩存的數據,然後生成一條重做日誌並寫入重做日誌緩存,當事務真正提交時,MySQL 會將重做日誌緩存中的內容刷新到重做日誌文件,再將記憶體中的數據更新到磁碟上,圖中的第 4、5 步就是在事務提交時執行的。
在 InnoDB 中,重做日誌都是以 512 位元組的塊的形式進行存儲的,同時因為塊的大小與磁碟扇區大小相同,所以重做日誌的寫入可以保證原子性,不會由於機器斷電導致重做日誌僅寫入一半並留下臟數據。
除了所有對資料庫的修改會產生重做日誌,因為回滾日誌也是需要持久存儲的,它們也會創建對應的重做日誌,在發生錯誤後,資料庫重啟時會從重做日誌中找出未被更新到資料庫磁碟中的日誌重新執行以滿足事務的持久性。
- 回滾日誌和重做日誌
到現在為止我們瞭解了 MySQL 中的兩種日誌,回滾日誌(undo log)和重做日誌(redo log);在資料庫系統中,事務的原子性和持久性是由事務日誌(transaction log)保證的,在實現時也就是上面提到的兩種日誌,前者用於對事務的影響進行撤銷,後者在錯誤處理時對已經提交的事務進行重做,它們能保證兩點:
1.發生錯誤或者需要回滾的事務能夠成功回滾(原子性);
2.在事務提交後,數據沒來得及寫會磁碟就宕機時,在下次重新啟動後能夠成功恢複數據(持久性);
在資料庫中,這兩種日誌經常都是一起工作的,我們可以將它們整體看做一條事務日誌,其中包含了事務的 ID、修改的行元素以及修改前後的值。
一條事務日誌同時包含了修改前後的值,能夠非常簡單的進行回滾和重做兩種操作,在這裡我們也不會對重做和回滾日誌展開進行介紹,可能會在之後的文章談一談資料庫系統的恢復機制時提到兩種日誌的使用。
隔離性
資料庫允許多個併發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務併發執行時由於交叉執行而導致數據的不一致。事務隔離分為不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重覆讀(repeatable read)和串列(Serializable)。
讀未提交(Read uncommitted):別人改數據的事務尚未提交,我在我的事務中也能讀到。
讀已提交(read committed):別人改數據的事務已經提交,我在我的事務中才能讀到。
可重覆讀(repeatable read):別人改數據的事務已經提交,我在我的事務中也不去讀。
串列(Serializable):我的事務尚未提交,別人就別想改數據。
隔離級別實現
資料庫對於隔離級別的實現就是使用併發控制機制對在同一時間執行的事務進行控制,限制不同的事務對於同一資源的訪問和更新,而最重要也最常見的併發控制機制,在這裡我們將簡單介紹三種最重要的併發控制器機制的工作原理。
- 鎖
鎖是一種最為常見的併發控制機制,在一個事務中,我們並不會將整個資料庫都加鎖,而是只會鎖住那些需要訪問的數據項, MySQL 和常見資料庫中的鎖都分為兩種,共用鎖(Shared)和互斥鎖(Exclusive),前者也叫讀鎖,後者叫寫鎖。
讀鎖保證了讀操作可以併發執行,相互不會影響,而寫鎖保證了在更新資料庫數據時不會有其他的事務訪問或者更改同一條記錄造成不可預知的問題。
- 時間戳
除了鎖,另一種實現事務的隔離性的方式就是通過時間戳,使用這種方式實現事務的資料庫,例如 PostgreSQL 會為每一條記錄保留兩個欄位;讀時間戳中報錯了所有訪問該記錄的事務中的最大時間戳,而記錄行的寫時間戳中保存了將記錄改到當前值的事務的時間戳。
使用時間戳實現事務的隔離性時,往往都會使用樂觀鎖,先對數據進行修改,在寫回時再去判斷當前值,也就是時間戳是否改變過,如果沒有改變過,就寫入,否則,生成一個新的時間戳並再次更新數據,樂觀鎖其實並不是真正的鎖機制,它只是一種思想,在這裡並不會對它進行展開介紹。
- 多版本和快照隔離
通過維護多個版本的數據,資料庫可以允許事務在數據被其他事務更新時對舊版本的數據進行讀取,很多資料庫都對這一機制進行了實現;因為所有的讀操作不再需要等待寫鎖的釋放,所以能夠顯著地提升讀的性能,MySQL 和 PostgreSQL 都對這一機制進行自己的實現,也就是 MVCC,雖然各自實現的方式有所不同,MySQL 就通過文章中提到的回滾日誌實現了 MVCC,保證事務並行執行時能夠不等待互斥鎖的釋放直接獲取數據。