Atomicity(原子性)html
一個事務必須被視爲一個不可分割的最小工做單位,整個事務中的全部操做要麼所有提交成功,要麼所有失敗回滾。c++
Consistency(一致性)sql
數據庫老是從一個一致性狀態轉換到另外一個一致性狀態,事務執行以前和執行以後都必須處於一致性狀態。數據庫
Isolation(隔離性)segmentfault
一般來講,一個事務所作的修改在最終提交以前,對其它事務是不可見的。關於事務的隔離性,數據庫提供了多種隔離級別。併發
Durability(持久性)性能
一旦事務提交,則其所作的修改就會永久保存到數據庫中。即使是數據庫系統遇到故障的狀況下也不會丟失。this
一個事務正在對一條記錄進行修改,在這個事務完成並提交前,這條記錄的數據就處於不一致狀態。這時,另外一個事務也來讀取同一條記錄,若是不加控制,第二個事務讀取了這些「髒」數據,並據此作進一步的處理,就會產生未提交的數據依賴關係。spa
時間 | 事務A | 事務B |
---|---|---|
T1 | 開啓事務 | 開啓事務 |
T2 | 查詢帳戶餘額爲1000 | |
T3 | 充值500,餘額修改成1500 | |
T4 | 查詢餘額爲1500 | |
T5 | 撤銷事務,餘額改回1000 | |
T6 | 匯入500,餘額修改成2000 | |
T7 | 提交事務 |
一個事務在讀取某些數據後的某個時間,再次讀取之前讀過的數據,卻發現其讀出的數據已經發生了變動、或者某些記錄已經被刪除了。3d
時間 | 事務A | 事務B |
---|---|---|
T1 | 開啓事務 | 開啓事務 |
T2 | select * from user where user_id=100 假設爲小明的用戶信息 | |
T3 | 將user_id=100的用戶信息對應的年齡修改成18 | |
T4 | 提交事務 | |
T5 | 再次查詢發現用戶的年齡變動了 | |
T6 | ... | |
T7 | 提交事務 |
一個事務按相同的查詢條件從新讀取之前檢索過的數據,卻發現其它事務插入了知足其查詢條件的新數據。
時間 | 事務A | 事務B |
---|---|---|
T1 | 開啓事務 | 開啓事務 |
T2 | select * from user where age=18 假設獲得兩條記錄 | |
T3 | 向user表插入一條age=18的新記錄 | |
T4 | 提交事務 | |
T5 | 再次查詢獲得三條記錄 | |
T6 | .. | |
T7 | 提交事務 |
SQL標準定義了4類隔離級別,每一種級別都規定了一個事務中所作的修改,哪些在事務內和事務間是可見的,哪些是不可見的。
全部事務均可以看到其它未提交事務的執行結果,該隔離級別通常不會使用。
一個事務只能看到已經提交的事務所作的變動。
確保同一事務的多個實例在併發讀取數據時會看到相同的數據行。
徹底串行化讀,每次讀都須要得到表級共享鎖,讀寫相互阻塞。
隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
Read Uncommited | Yes | Yes | Yes |
Read Committed | No | Yes | Yes |
Repeatable Read | No | No | Yes |
Serializable | No | No | No |
髒讀、不可重複讀和幻讀都是數據庫讀一致性問題,須要由數據庫提供必定的事務隔離機制來解決。
(1)鎖機制
解決寫-寫衝突問題。在讀取數據前,對其加鎖,防止其它事務對該數據進行修改。
悲觀鎖
每每依靠數據庫提供的鎖機制。
樂觀鎖
大可能是基於數據版本記錄機制來實現。
(2)MVCC多版本併發控制
解決讀-寫衝突問題。不用加鎖,經過必定機制生成一個數據請求時間點時的一致性數據快照, 並用這個快照來提供必定級別 (語句級或事務級) 的一致性讀取。這樣在讀操做的時候不須要阻塞寫操做,寫操做時不須要阻塞讀操做。
Mysql的大多數事務型存儲引擎實現都不是簡單的行級鎖,基於併發性能考慮,通常都實現了MVCC多版本併發控制。MVCC是經過保存數據在某個時間點的快照來實現的。無論事務執行多長時間,事務看到的數據都是一致的。
讀操做分紅兩類:快照讀和當前讀。
快照讀:簡單的select操做屬於快照讀,不加鎖。
當前讀:特殊的讀操做,插入/更新/刪除操做,屬於當前讀,須要加鎖。
innodb存儲引擎中,每行數據都包含了一些隱藏字段:DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR和DELETE_BIT。
insert
建立一條記錄,DB_TRX_ID爲當前事務ID,DB_ROLL_PTR爲NULL。
delete
將當前行的DB_TRX_ID設置爲當前事務ID,DELELE_BIT設置爲1。
update 複製一行,新行的DB_TRX_ID爲當前事務ID,DB_ROLL_PTR指向上個版本的記錄,事務提交後DB_ROLL_PTR設置爲NULL。
select
一、只查找建立早於當前事務ID的記錄,確保當前事務讀取到的行都是事務以前就已經存在的,或者是由當前事務建立或修改的;
二、行的DELETE BIT爲1時,查找刪除晚於當前事務ID的記錄,確保當前事務開始以前,行沒有被刪除。
Mysql的一致性讀是經過read view結構來實現。 read view主要是用來作可見性判斷的,它維護的是本事務不可見的當前其餘活躍事務。其中最先的事務ID爲up_limit_id
,最遲的事務ID爲low_limit_id
。
trx_id_t low_limit_id;
/*!< The read should not see any transaction with trx id >= this value. In other words, this is the "high water mark". */
trx_id_t up_limit_id;
/*!< The read should see all trx ids which are strictly smaller (<) than this value. In other words, this is the "low water mark". */
複製代碼
能夠參考知乎這個答案來理解。low_limit_id應該是當前系統還沒有分配的下一個事務ID(從這個語義來更容易理解),也就是目前已經出現過的事務ID的最大值+1。
MySQL 在 RC 隔離級別下是如何實現讀不阻塞的? 呵呵一笑百媚生的答案
假設要讀取的行的最後提交事務id(即當前數據行的穩定事務id)爲 trx_id,可見性比較過程以下:
Repeatable Read和Read Committed隔離級別都是基於read view來實現,不一樣之處在於:
Repeatable Read
read view是在執行事務中第一條select語句的瞬間建立,後續全部的select都是複用這個對象,因此能保證每次讀取的一致性。(可重複讀的語義)
Read Committed
事務中每條select語句都會建立read view,這樣就能夠讀取到其它事務已經提交的內容。
對於InnoDB來講,Repeatable Read雖然比Read Committed隔離級別高,開銷反而相對較小。