事務基本特性ACID分別是:
原子性(Atomicity)
指的是一個事務中的操作要么全部成功,要么全部失敗。
一致性(Consistency)
指的是數據庫總是從一個一致性的狀態轉換到另外一個一致性的狀態。比如A轉賬給B100塊錢,假設中間sql執行過程中系統崩潰A也不會損失100塊,因為事務沒有提交,修改也就不會保存到數據庫。
隔離性(Isolation)
指的是一個事務的修改在最終提交前,對其他事務是不可見的。
持久性(Durability)
指的是一旦事務提交,所做的修改就會永久保存到數據庫中。
ACID靠什么保證的呢
A原子性由undo log日志保證,它記錄了需要回滾的日志信息,事務回滾時撤銷已經執行成功的sql;
C一致性一般由代碼層面來保證;(如tcc兩階段提交,事務回滾)
I隔離性由MVCC來保證;(多版本並發控制,保存當前時間快照)
D持久性由內存+redo log來保證,mysql修改數據同時在內存和redo log記錄這次操作,事務提交的時候通過redo log刷盤,宕機的時候可以從redo log恢復;
而隔離性有4個隔離級別,分別是:
1、read uncommit 讀未提交,可能會讀到其他事務未提交的數據,也叫做臟讀。
用戶本來應該讀取到id=1的用戶age應該是10,結果讀取到了其他事務還沒有提交的事務,結果讀取結果age=20,這就是臟讀。
2、read commit 讀已提交,兩次讀取結果不一致,叫做不可重復讀。
不可重復讀解決了臟讀的問題,他只會讀取已經提交的事務。
用戶開啟事務讀取id=1用戶,查詢到age=10,再次讀取發現結果=20,在同一個事務里同一個查詢讀取到不同的結果叫做不可重復讀。
3、repeatable read 可重復復讀,這是mysql的默認級別,就是每次讀取結果都一樣,但是有可能產生幻讀。
4、serializable 串行,一般是不會使用的,他會給每一行讀取的數據加鎖,會導致大量超時和鎖競爭的問題。
多版本並發控制MVCC
- MVCC是行級鎖的一個變種,但是它在很多情況下避免了加鎖操作,因此開銷更低。雖然實現機制有所不同,但大都實現了非阻塞的讀操作,寫操作也只鎖定必要的行。
- MVCC的實現,是通過保存數據在某個時間點的快照來實現的。也就是說,不管需要執行多長時間,每個事務看到的數據都是一致的。根據事務開始的時間不同,每個事務對同張表,同一時刻看到的數據可能是不一樣的。
- InnoDB的MVCC,是通過在每行記錄后面保存兩個隱藏的列來實現的。這兩個列,一個保存了行的創建時間,一個保存行的過期時間(或刪除時間)。當然存儲的並不是實際的時間值,而是系統版本號。每開始一個新的事務,系統版本號都會自動遞增。事務開始時刻的系統版本號會作為事務的版本號,用來和查詢到的每行記錄的版本號進行比較。
- MVCC只在可重復讀和讀提交的隔離級別生效。其它兩個級別都不兼容
事務到底是隔離的還是不隔離的
innodb支持RC(讀提交)和RR(可重復讀)隔離級別實現是用的一致性視圖(consistent read view)
事務在啟動時會拍一個快照,這個快照是基於整個庫的。基於整個庫的意思就是說一個事務內,整個庫的修改對於該事務都是不可見的(對於快照讀的情況)。如果在事務內select t表,另外的事務執行了DDL t表,根據發生時間,要嗎鎖住要嘛報錯
事務是如何實現的MVCC呢?
每個事務都有一個事務ID,叫做transaction id(嚴格遞增)
事務在啟動時,找到已提交的最大事務ID記為up_limit_id。
事務在更新一條語句時,比如id=1改為了id=2.會把id=1和該行之前的row trx_id寫到undo log里。並且在數據頁上把id的值改為2,並且把修改這條語句的transaction id記在該行行頭。
再定一個規矩,一個事務要查看一條數據時,必須先用該事務的up_limit_id與該行的transaction id做比對
如果up_limit_id>=transaction id,那么可以看.如果up_limit_id<transaction id,則只能去undo log里去取。去undo log查找數據的時候,也需要做比對,必須up_limit_id>transaction id,才返回數據
什么是當前讀,
由於當前讀都是先讀后寫,只能讀當前的值,所以認為當前讀.會更新事務內的up_limit_id為該事務的transaction id
為什么RR能實現可重復讀而RC不能,分兩種情況
快照讀的情況下,rr(可重復讀)不能更新事務內的up_limit_id,而rc(讀提交)每次會把up_limit_id更新為快照讀之前最新已提交事務的transaction id,則rc(讀提交)不能可重復讀
當前讀的情況下,rr(可重復讀)是利用record lock+gap lock來實現的,而rc(讀提交)沒有gap,所以rc不能可重復讀
幻讀是什么,幻讀有什么問題
幻讀指的是一個事務在前后兩次查詢同一個范圍的時候,后一次查詢看到了前一次查詢沒有看到的行。(如假設張三開啟了事務,select了兩次,第二次查出來的數據比第一次的多了,如同出現了幻覺一樣)
在可重復讀隔離級別下,普通的查詢是快照讀,是不會看到別的事務插入的數據的。因此,幻讀在“當前讀”下才會出現。
innodb的默認事務隔離級別是rr(可重復讀)。它的實現技術是mvcc。基於版本的控制協議。該技術不僅可以保證innodb的可重復讀,而且可以防止幻讀。但是它防止的是快照讀,也就是讀取的數據雖然是一致的,但是數據是歷史數據。如何做到保證數據是一致的(也就是一個事務,其內部讀取對應某一個數據的時候,數據都是一樣的),同時讀取的數據是最新的數據。innodb提供了一個間隙鎖的技術。也就是結合grap鎖與行鎖,達到最終目的。當使用索引進行插入的時候,innodb會將當前的節點和上一個節點加鎖。這樣當進行select的時候,就不允許加x鎖。那么在進行該事務的時候,讀取的就是最新的數據。
實現:
1. 快照讀(snapshot read)
簡單的select操作,其他的都屬於當前讀。
實現為mvcc,生成快照數據,實現可重復讀,不會出現幻讀。
2.當前讀(current read)
select ... lock in share mode
select ... for update
insert
update
delete
在RR級別下,快照讀是通過MVVC(多版本控制)和undo log來實現的,當前讀是通過加record lock(記錄鎖)和gap lock(間隙鎖)來實現的。
所以從上面的顯示來看,如果需要實時顯示數據,還是需要通過加鎖來實現。這個時候會使用next-key技術來實現。
總結:在mysql中,提供了兩種事務隔離技術,第一個是mvcc,第二個是next-key技術。這個在使用不同的語句的時候可以動態選擇。不加lock inshare mode之類的就使用mvcc。否則使用next-key。mvcc的優勢是不加鎖,並發性高。缺點是不是實時數據。next-key的優勢是獲取實時數據,但是需要加鎖。同時需要注意幾點:1.事務的快照時間點是以第一個select來確認的。所以即便事務先開始。但是select在后面的事務的update之類的語句后進行,那么它是可以獲取后面的事務的對應的數據。2.mysql中數據的存放還是會通過版本記錄一系列的歷史數據,這樣,可以根據版本查找數據。
next key lock
Next-Key鎖是索引記錄上的記錄鎖和索引記錄之前間隙上的間隙鎖的組合。
假設一個索引包含值10、11、13和20。此索引可能的next-key鎖包括以下區間:(-∞, 10],(10, 11],(11, 13],(13, 20],(20, ∞ ]
對於最后一個間隙,∞不是一個真正的索引記錄,因此,實際上,這個next-key鎖只鎖定最大索引值之后的間隙。所以,Next-Key 的鎖的范圍都是左開右閉的。Next-Key Lock和Gap Lock一樣,只有在InnoDB的RR隔離級別中才會生效。
Repeatable Reads能解決幻讀
很多人看過網上的關於數據庫事務級別的介紹,會認為MySQL中Repeatable Reads能解決不可重復讀的問題,但是不能解決幻讀,只有Serializable才能解決。但其實,這種想法是不對的。
因為MySQL跟標准RR不一樣,標准的Repeatable Reads確實存在幻讀問題,但InnoDB中的Repeatable Reads是通過next-key lock解決了RR的幻讀問題的。因為我們知道,因為有了next-key lock,所以在需要加行鎖的時候,會同時在索引的間隙中加鎖,這就使得其他事務無法在這些間隙中插入記錄,這就解決了幻讀的問題。
關於這個問題,引起過廣泛的討論,可以參考:https://github.com/Yhzhtk/note/issues/42 ,這里有很多大神發表過自己的看法。
MySQL的加鎖原則
Record Lock、Gap Lock和Next-Key Lock,但是並沒有說明加鎖規則。關於加鎖規則,看了丁奇大佬的《MySQL實戰45講》中的文章之后理解的,他總結的加鎖規則里面,包含了兩個“原則”、兩個“優化”和一個“bug”:
原則 1:加鎖的基本單位是 next-key lock。是一個前開后閉區間。原則 2:查找過程中訪問到的對象才會加鎖。優化 1:索引上的等值查詢,給唯一索引加鎖的時候,next-key lock 退化為行鎖。優化 2:索引上的等值查詢,向右遍歷時且最后一個值不滿足等值條件的時候,next-key lock 退化為間隙鎖。一個 bug:唯一索引上的范圍查詢會訪問到不滿足條件的第一個值為止。
rr級別下當前讀加鎖問題,能否解決幻讀問題參考更多其他文章。
參考:https://www.cnblogs.com/cat-and-water/p/6427612.html
https://mp.weixin.qq.com/s/aG3f-bqEof3ww156T1fBzg
https://qinxuewu.github.io/docs/#/2019/MySQL%E5%AE%9E%E6%88%9845%E8%AE%B2%E7%AC%94%E8%AE%B0?id=%e5%b0%8f%e7%bb%93
https://mp.weixin.qq.com/s/mvG0wVr2kpbM8iqfZnimiQ