併發問題,
丟失修改,不可重複讀,幻讀,髒讀
- 不可重複讀
有個事務T,T讀了兩次數據,在兩次中間有人update/delete了數據,導致T兩次讀的結果不一致 - 幻讀
(在高性能mysql裏把這個歸爲不可重複讀)同上讀了兩次,中間有人insert了一條數據,導致兩次結果不一致 - 髒讀
即讀到其他事務未提交的數據 - 丟失修改
兩個事務先後讀改再提交,導致最終結果錯誤(?)
事務隔離級別有四種,RU,RC,RR和SI
三段鎖協議
一段鎖:在改前加X鎖,T完釋放,可解決丟失修改(不可以啊?)
二段鎖:讀前S鎖,讀完釋放,解決髒讀
三段鎖,讀前S鎖,T完釋放,解決不可重複讀
以上是理論,實際InnoDB的隔離級別的實現與這些大不相同,
mysql的實現
RC
在RC和RR下都用到了MVCC.
MySQL的讀已提交實際是語句級別快照,即快照讀的時候讀的總是最新的快照.讀的策略與可重複讀類似。
RC寫鎖的使用方式。這裏不需要GAP LOCK,只使用記錄鎖(所以會幻讀)。並且事務只持有被UPDATE/DELETE記錄的寫鎖(可重複讀需要保留全部寫鎖直到事務結束,而讀已提交只保留真正更改的)。
RR
RR下也用MVCC,和RC的區別是它讀的是事務開始時的快照.
RR下默認讀不加鎖而是用快照讀,只有寫才加鎖,讀寫互不阻塞,但會有Write Skew異常(TODO)。
RC和RR差別是:RC隔離級別下的事務在每次查詢的開始都會生成一個獨立的ReadView,而可重複讀隔離級別則在第一次讀的時候生成一個ReadView,之後的讀都複用之前的ReadView
在RR下一般下select默認是快照讀,除非用select for update 和select in share lock
- 其中select in share lock是用行鎖,
- select for update是用間隙鎖,即next lock算法,
- 快照讀,能保證read和update操作能並行,即用MVCC存儲每個事務的多個版本,故名字爲多版本控制
如果是快照讀,可重複讀級別下不存在幻讀;如果先用快照讀又用select...from...for update(locking read)讀就存在幻讀;如果兩次查詢都是select...from...for update不會幻讀。
next-key lock
next-key鎖=行鎖+間隙鎖, 來解決幻讀,是謂詞鎖predict lock的改進(?)
還有用previous lock(TODO)
next-key lock會在select for update觸發,鎖定[)左閉右開的旁邊兩個值範圍,左閉右開是因爲自增操作(不是很理解?)
record lock會鎖索引,沒有索引則鎖隱式的主鍵
對於唯一索引,InnoDB會把next-key lock降級爲Record lock
即:若是沒有索引的條件下,就獲取所有行,都加上行鎖,然後Mysql會再次過濾符合條件的的行並釋放鎖,
只有符合條件的行纔會繼續持有鎖。這樣性能也會消耗很大.
MVCC
在多版本存儲上,MySQL採用從新到舊(Newest To Oldest)的版本鏈。
B+Tree葉結點上,始終存儲的是最新的數據(可能是還未提交的數據)。
而舊版本數據,通過UNDO記錄(做DELTA)存儲在回滾段(Rollback Segment)裏。
每一條記錄都會維護一個ROW HEADER元信息,存儲有創建這條記錄的事務ID,一個指向UNDO記錄的指針。通過最新記錄和UNDO信息,可以還原出舊版本的記錄。
用白話文說,每一行數據會有多種版本,而舊版本信息是存在undo log裏的,要用的時候通過對應的undo log來推導出來
可串行化(Serializable)
在可串行化級別上,MySQL執行S2PL併發控制協議, 一階段申請,一階段釋放。讀寫都要加鎖。
參考資料
TODO
next lock算法細則next key lock會鎖定多大的範圍呢間隙鎖和next lock區別,next lock=行鎖+間隙鎖吧next key lock普通的select都會鎖定這個範圍嗎,RR級別 select不用for update 會用next-key lock嗎write skew產生的幻讀,知乎的解釋ReadView是什麼?MVCC和快照的實現,裏面也有undo和readview關係- 數據庫面經
- 各種衝突畫時間圖來鞏固下
- snapshot(readview)和可見性的判斷