持久性,咱們就不講了,易懂。java
在同一個事務內部的一組操做必須所有執行成功(或者所有失敗)。redis
爲了保證事務操做的原子性,必須實現基於日誌的REDO/UNDO機制:將全部對數據的更新操做都寫入日誌,若是一個事務中的一部分操做已經成功,但之後的操做,因爲斷電/系統崩潰/其它的軟硬件錯誤而沒法繼續,則經過回溯日誌,將已經執行成功的操做撤銷,從而達到「所有操做失敗」的目的。 最多見的場景是,數據庫系統崩潰後重啓,此時數據庫處於不一致的狀態,必須先執行一個crash recovery的過程:讀取日誌進行REDO(重演將全部已經執行成功但還沒有寫入到磁盤的操做,保證持久性),再對全部到崩潰時還沒有成功提交的事務進行UNDO(撤銷全部執行了一部分但還沒有提交的操做,保證原子性)。crash recovery結束後,數據庫恢復到一致性狀態,能夠繼續被使用。sql
某個應用在執行轉賬的數據庫操做時,必須在同一個事務內部調用對賬戶A和賬戶B的操做,才能保證數據的一致性。數據庫
可是,原子性並不能徹底保證一致性。在多個事務並行進行的狀況下,即便保證了每個事務的原子性,仍然可能致使數據不一致的結果。 例如,事務1須要將100元轉入賬號A:先讀取賬號A的值,而後在這個值上加上100。可是,在這兩個操做之間,另外一個事務2修改了賬號A的值,爲它增長了100元。那麼最後的結果應該是A增長了200元。但事實上,事務1最終完成後,賬號A只增長了100元,由於事務2的修改結果被事務1覆蓋掉了。安全
簡而言之,就是:原子性僅可以保證單個事務的一致性。就像redis同樣,也只能保證單操做的線程安全,並不能保證多操做下的線程安全。併發
按照我我的的理解,在事務處理的ACID屬性中,一致性是最基本的屬性,其它的三個屬性都爲了保證一致性而存在的。性能
咱們舉個反例來理解下一致性概念。例如:從賬戶A轉一筆錢到賬戶B上,若是賬戶A上的錢減小了,而賬戶B上的錢卻沒有增長,那麼咱們認爲此時數據處於不一致的狀態。線程
爲了保證併發狀況下的一致性,引入了隔離性,即保證每個事務可以看到的數據老是一致的,就好象其它併發事務並不存在同樣。日誌
數據庫四種隔離級別,以及常見的幾種讀異常,你們應該都是耳熟能詳的,但數據庫底層是怎麼實現隔離性的呢?都採用了哪些技術呢? 主要有兩個技術:MVCC(多版本併發控制)和鎖。code
多版本併發控制,顧名思義,在併發訪問的時候,數據存在版本的概念,能夠有效地提高數據庫併發能力,常見的數據庫如MySQL、MS SQL Server、IBM DB二、Hbase、MongoDB等等都在使用。
簡單講,若是沒有MVCC,當想要讀取的數據被其餘事務用排它鎖鎖住時,只能互斥等待;而這時MVCC能夠經過提供歷史版本從而實現讀取被鎖的數據的歷史版本,從而避免了互斥等待。
InnoDB採用的MVCC實現方式是:在須要時,經過undo日誌構造出歷史版本。
若事務T對數據對象A加上S鎖,則事務T只能讀A;其餘事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這就保證了其餘事務能夠讀A,但在T釋放A上的S鎖以前不能對A作任何修改。
若事務T對數據對象A加上X鎖,則只容許T讀取和修改A,其它任何事務都不能再對A加任何類型的鎖,直到T釋放A上的鎖。它防止任何其它事務獲取資源上的鎖,直到在事務的末尾將資源上的原始鎖釋放爲止。在更新操做(INSERT、UPDATE 或 DELETE)過程當中始終應用排它鎖。
注意:排他鎖會阻止其它事務再對其鎖定的數據加讀或寫的鎖,可是不加鎖的就沒辦法控制了。
行鎖,顧名思義,是加在索引行
(對!是索引行!不是數據行!)上的鎖。好比select * from user where id=1 and id=10 for update
,就會在id=1
和id=10
的索引行上加Record Lock。
間隙鎖,它會鎖住兩個索引之間的區域。好比select * from user where id>1 and id<10 for update
,就會在id爲(1,10)的索引區間上加Gap Lock。
也叫間隙鎖,它是Record Lock + Gap Lock造成的一個閉區間鎖。好比select * from user where id>=1 and id<=10 for update
,就會在id爲[1,10]的索引閉區間上加Next-Key Lock。
這樣組合起來就有,行級共享鎖,表級共享鎖,行級排它鎖,表級排它鎖。
在數據庫增刪改查四種操做中,insert、delete和update都是會加排它鎖(Exclusive Locks)的,而select只有顯式聲明纔會加鎖:
不一樣的隔離級別是在數據可靠性和併發性之間的均衡取捨,隔離級別越高,對應的併發性能越差,數據越安全可靠。
顧名思義,事務之間能夠讀取彼此未提交的數據。機智如你會記得,在前文有說到全部寫操做都會加排它鎖,那還怎麼讀未提交呢?
機智如你,前面咱們介紹排它鎖的時候,有這種說明: 排他鎖會阻止其它事務再對其鎖定的數據加讀或寫的鎖,可是對不加鎖的讀就不起做用了。
READ UNCOMMITTED
隔離級別下, 讀不會加任何鎖。而寫會加排他鎖,併到事務結束以後釋放。
實例1:
查-寫:查並無阻止寫,代表查確定並無加鎖,要不寫確定就阻塞了。寫很明顯,會加排它鎖的。
實例2: 寫-寫:阻塞,代表,寫會加排它鎖。
顧名思義,事務之間能夠讀取彼此已提交的數據。
InnoDB在該隔離級別(READ COMMITTED)寫數據時,使用排它鎖, 讀取數據不加鎖而是使用了MVCC機制。
所以,在讀已提交的級別下,都會經過MVCC獲取當前數據的最新快照,不加任何鎖,也無視任何鎖(由於歷史數據是構造出來的,身上不可能有鎖)。
可是,該級別下仍是遺留了不可重複讀和幻讀問題: MVCC版本的生成時機: 是每次select時。這就意味着,若是咱們在事務A中執行屢次的select,在每次select之間有其餘事務更新了咱們讀取的數據並提交了,那就出現了不可重複讀,即:重複讀時,會出現數據不一致問題,後面咱們會講解超支現象,就是這種引發的。
READ COMMITTED級別不一樣的是MVCC版本的生成時機,即:一次事務中只在第一次select時生成版本,後續的查詢都是在這個版本上進行,從而實現了可重複讀。
可是由於MVCC的快照只對讀操做有效,對寫操做無效,舉例說明會更清晰一點: 事務A依次執行以下3條sql,事務B在語句1和2之間,插入10條age=20的記錄,事務A就幻讀了。
1. select count(1) from user where age=20; -- return 0: 當前沒有age=20的 2. update user set name=test where age=20; -- Affects 10 rows: 由於事務B剛寫入10條age=20的記錄,而寫操做是不受MVCC影響,能看到最新數據的,因此更新成功,而一旦操做成功,這些被操做的數據就會對當前事務可見 3. select count(1) from user where age=20; -- return 10: 出現幻讀
REPEATABLE READ級別,能夠防止大部分的幻讀,但像前邊舉例讀-寫-讀的狀況,使用不加鎖的select依然會幻讀。
大殺器,該級別下,會自動將全部普通select
轉化爲select ... lock in share mode
執行,即針對同一數據的全部讀寫都變成互斥的了,可靠性大大提升,併發性大大下降。
讀-寫,寫-寫均互斥。
4)總結:幾類讀異常
讀-寫-讀,引發的異常
髒讀:讀取了髒數據(不存在的數據)。 事務一讀 事務二寫(未提交) 事務二讀(髒數據) 事務二回滾
不可重複讀:既能夠讀取修改的數據,也能夠讀取新增的數據(幻讀)。 事務一讀 事務二寫(更新已提交) 事務二讀(數據不一致,不可重複讀)
幻讀:僅能夠讀取新增的數據,可是沒法讀取修改的數據; 事務一讀 事務二寫(新增已提交) 事務二讀(數據不一致,幻讀)
附命令
查看錶的加鎖狀況: select * from information_schema.INNODB_LOCKS;
事務狀態 select * from information_schema.INNODB_TRX;
更多資料分享,問題諮詢,能夠入羣討論:375412858 請入羣索要代碼: