從本源來理解比較容易理解,如果只是描述概念和定義,容易讓人雲裡霧裡找不到方向.正好這兩天在瀏覽mysql的文件,我可以簡單在這裡總結一下,幫助其他還沒有理解的朋友,如果有錯誤也麻煩幫忙指正.
先講一點背景知識:
首先明確一點,資料庫的命令的執行者的封裝基本抽象是transaction,語句的執行都會有對應的transaction物件,並且都會有對應的id來標識不同的transaction.transaction id按照不同的時序來分配, 通俗點說,時間上來執行的早的transaction id小一點, 晚的transaction id大一點.不是說只有我們手動去start transaction這樣才會建立transaction.我們執行乙個簡單的autocommit型別的語句,比如insert或者update語句,都會對應生成乙個新的事務.
所以,所有sql語句執行皆有其對應的transaction id, transaction id的大小可以表示sql語句執行發生的早晚.
mysql提供了多版本讀取能力.什麼叫多版本?就是說表中乙份儲存的資料行會有多個版本,這個版本對應的version就是transaction id.當行資料被某個transaction變更時, 這個行會有個隱含的hidden column中記錄了這個更新者的transaction id.舊的資料在被覆蓋的同時,會儲存到乙個undolog的檔案中,這個檔案中就是儲存著每一行的版本資料鏈表,沿著這個鍊錶走我們可以走過這一行資料之前的版本變更.
(這個undolog資料量不需要那麼大,比如乙個行資料的更新者transaction id小於所有server中活躍的transaction ids,那麼這個行資料的undolog中的資料就可以刪除掉,因為不會有transaction來去查詢這個行記錄的undolog)
一,現在來說什麼是可重複讀:
當開始乙個新的transaction,這個transaction會分配乙個新的transactionid,此時在這個transaction內部如果我們執行一條普通的select語句,根據select語句的condition,我們會找到一些滿足條件的行記錄,先不著急返回:
1.如果此時是mysql預設的repeatable read隔離模式
每個匹配的行記錄,我們從前面說的hidden column中找到對應的最近一次執行更新操作的操作者transaction id,我們將這個id(也就是版本),和我們當前的transaction id進行比較,如果大於執行語句所屬的transaction的id,那麼就需要去undolog檔案中去尋找舊的版本,一直找到小於當前transaction id的版本的行記錄,這個就作為快照資料返回.其他的行記錄都是這樣來處理的.
在repeatable read隔離模式中,所有的普通select語句(consistent read)(非select ... for update的語句)都會進行這樣的比對過程來返回資料.也就是說,即使在我們當前這個事務執行過程中,有其它事務執行插入了新的資料能夠滿足我們的查詢條件,但因為這個資料的版本(transaction id)大於我們當前事務的id,我們是查詢不到這個資料的.
這也就是成為可重複查的原因,不管多少次查詢,每次讀取的都是同樣的版本的資料,也就是字面的意思.
2.如果此時是read committed隔離模式
對於普通的查詢select語句不再有上面這個版本的限制,每次查詢,只需要返回滿足條件的最新的行記錄即可.所以此時就不是可重複讀,也就是說我們可以看到別的事務提交的資料(所以名字叫read committed嘛)
二, 然後來說什麼是幻讀:
幻讀的字面意思很簡單,就是在事務內(這裡強調同乙個事務)兩次查詢語句(注意是select ...for...update語句,不是前面的普通查詢語句),讀取到了不同的資料.
這個從情理上來看是很正常的時情,首先因為資料庫是乙個支援大量併發任務的服務,那麼我們在事務執行的過程中,新的資料插入並發生變化也是很正常的事情,所以這不是bug.
注意,這裡說到了併發, 在程式語言裡面我們知道,出現了併發問題的乙個最最常見的情況就是,check and do這樣的操作,這樣不是併發操作安全的,因為check和do的過程中可能會插入其它的操作,所以當我們進行do操作的時候,先前的條件可能已經不滿足了.
那麼在程式語言中我們是怎麼解決問題的呢?
也就是加鎖,對我們關注的資料(物件導向語言中的物件)進行加鎖操作,其實也就是獨佔,我這個任務在拿著這個鎖的時候,別的人都靠邊站(都阻塞在那裡),我這個任務做完了,其它任務才可以去做.
在資料庫中也是同樣的問題,我們的select..where..condition..for update也是先圈定乙個感興趣的資料(滿足condition的行資料),然後進行update操作,在這個事務進行過程中面臨的也是併發的問題.
那麼參考程式語言的做法,我們的方式也是獨佔,獨佔的目標是什麼呢?
只是滿足條件的行資料嗎?
當然不止,比如condition: a>5 and a<10,目前只有一條記錄a=7,如果我們只鎖這個記錄可以嗎?不可以,因為其它事務可能還會做新的插入操作,比如插入一條a=9的記錄,假如我們的業務邏輯是,如果當前a在5-10的區間中只有一條記錄,我們就可以刪除這個記錄(這個業務邏輯很奇怪,先假設是這樣),在上一次查詢的時候是只有一條行記錄,我們認為滿足了條件,現在我們就要刪除這個記錄,卻發現5-10中有了2條記錄,那麼此時的刪除操作就是乙個誤操作....
資料庫解決這個問題的辦法就是,對這個區間都加鎖,不僅僅是已有的行記錄,空的那些a的值[8,9]都會加上鎖.也就是gap lock,這個時候我們當前的事務就達到了對這個區間獨佔的目的,其它的事務在我們處理過程中就無法插入新的資料了: ).
當然, 是否獨佔,加不加區間的鎖,這些mysql給我們了自由選擇的權力,在read commited下,就不會加上區間鎖,
只會鎖住已有的記錄,所以此時如果有其它事務插入新的資料,當然也可以成功.如果在reapeatable-read下,就是會執行上面說的獨佔操作,其它想在這個區間插入資料的事務就得等在那裡了(阻塞住了).
講到這裡,我覺得幻讀和可重複讀的關係應該理清了,
1.repeatable read模式下,我們執行select...for..update獨佔了滿足條件的記錄,阻斷了其它事務的變更,不會出現幻讀的情況.
在read committed模式下,不獨佔,會有併發問題,會出現查到新的資料的問題.
阻斷幻讀的目的往往在於我們有check-and-do這樣的業務邏輯需求,來實現更進一步嚴格的原子性需求.
2.對於可重複讀,是資料庫多版本的一種福利,幫助我們實現在事務中能夠實現鎖定時間點讀取快照的目的.
最後說乙個區間鎖的場景,
比如我們需要業務場景中經常會有類似這樣乙個需求,當表中存在滿足某個條件的一行資料,我們就不做.如果不存在我們就插入一條記錄.此時,我們就可以利用上面說的解決幻讀的辦法,使用repeatable模式,因為它會鎖定對應的條件,在我們select...for..update過程中,可以保證不會有其它事務插入.這樣我們的表中就不會出現因為併發問題,導致無法實現唯一性的問題了.
讀者點評一下上述文章的幾個核心點
1.首先對於普通的增、修改、刪除操作mysql預設開啟乙個事務;不要以為沒有事務開啟,只是完成操作就結束了
2.如果資料庫開啟了可重複讀的隔離模式,那就需要知道以下幾個事情:
1)快照讀
我們執行一條普通的select查詢語句由於缺省會開啟乙個新事務,分配乙個事務id,那麼mysql會去判斷(undo檔案)自己是不是事務id最小的,如果不是,那麼儘管有比自己大的,有可能併發下,有新的資料插入,那也不管,只返回比自己小的資料!
2)間隙鎖
對於select ...for update 如果是區間查詢 比如 a>5 and a<10 mysql做法是啟用間隙鎖,它會選擇鎖住乙個區間範圍;此時是乙個獨佔的概念,另外的事務插入、修改都沒辦法進行,需要等待這個事務被釋放,起到乙個序列的概念!
髒讀 幻讀 不可重複讀和可重複讀
github 髒讀 幻讀 不可重複讀和可重複讀 即acid 隔離級別 髒讀 dirty read 不可重複讀 nonrepeatable read 幻讀 phantom read 未提交讀 read uncommitted 可能可能 可能已提交讀 read committed 不可能可能 可能可重複...
不可重複讀,可重複讀,幻讀,MVCC概念理解
是指在乙個事務內,多次讀同一資料。在這個事務還沒有結束時,另外乙個事務也訪問該同一資料。那麼,在第乙個事務中的兩次讀資料之間,由於第二個事務的修改,那麼第乙個事務兩次讀到的資料可能是不一樣的。這樣就發生了在乙個事務內兩次讀到的資料是不一樣的,因此稱為是不可重複讀。前提是非序列化 乙個事務在執行過程中...
mysql可重複讀和幻讀例項
mysql的預設事務級別是 可重複讀 其中可重複讀是通過mvcc來實現的又叫快照讀,在事務中的讀操作通過對當前的資料庫中記錄乙個版本,以後的讀操作只會讀取記錄的版本,因此相當於對資料庫的資料建立了乙個快照資料,因此叫做快照讀,其不用對資料庫中的資料進行加鎖又叫做樂觀鎖。同時rr事務級別的mysql通...