臟讀(Dirty Read)
某個事務已更新一份數據,另一個事務在此時讀取了同一份數據,由于某些原因,前一個進行了RollBack,則后一個事務所讀取的數據就會是不正確的。
不可重復讀(Non-repeatable read)
在一個事務的兩次查詢之中數據不一致,這可能是兩次查詢過程中間插入了一個事務更新了原有的數據。
幻讀(Phantom Read)
在一個事務的兩次查詢中數據筆數不一致,例如有一個事務查詢了幾列(Row)數據,而另一個事務卻在此時插入了新的幾列數據,先前的事務在接下來的查詢中,就會發現有幾列數據是它先前所沒有的。
事務隔離級別
由低到高依次為Read uncommitted、Read committed、Repeatable read、Serializable,這四個級別可以逐個解決臟讀、不可重復讀、幻讀這幾類問題
READ-UNCOMMITTED(讀取未提交): 最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致臟讀、不可重復讀和幻讀。
READ-COMMITTED(讀取已提交): 允許讀取并發事務已經提交的數據,可以阻止臟讀,但是不可重復讀和幻讀仍有可能發生。
REPEATABLE-READ(可重復讀): 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止臟讀和不可重復讀,但幻讀仍有可能發生。
SERIALIZABLE(可串行化): 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀。
Mysql 默認采用的 REPEATABLE_READ隔離級別
事務隔離機制的實現基于鎖機制和并發調度。其中并發調度使用的是MVVC(多版本并發控制),通過保存修改的舊版本信息來支持并發一致性讀和回滾等特性。
因為隔離級別越低,事務請求的鎖越少,所以大部分數據庫系統的隔離級別都是READ-COMMITTED(讀取提交內容):
但是InnoDB 存儲引擎默認使用 REPEATABLE-READ(可重讀)并不會有任何性能損失。
隔離級別與鎖的關系
-
在Read Uncommitted
讀操作:不加鎖,讀讀,讀寫,寫讀并行;
寫操作:加排他鎖且直到事務提交后才釋放。 -
在Read Committed級別下
讀操作:加共享鎖,操作完立即釋放; 短鎖
寫操作:加排他鎖且直到事務提交后才釋放;
讀操作不會阻塞其他事務讀或寫,寫操作會阻塞其他事務寫和讀,因此可以防止臟讀問題。 -
在Repeatable Read級別
讀操作:加S鎖且直到事務提交后才釋放; 長鎖
寫操作:加X鎖且直到事務提交后才釋放;
讀操作不會阻塞其他事務讀但會阻塞其他事務寫;
寫操作會阻塞其他事務讀和寫,因此可以防止臟讀、不可重復讀。 -
SERIALIZABLE 是限制性最強的隔離級別,讀寫數據都會鎖住整張表
讀操作:加X鎖且直到事務提交后才釋放; 長鎖
寫操作:加X鎖且直到事務提交后才釋放;
粒度為表鎖,也就是嚴格串行
事務實現原理
原子性、一致性、持久性通過數據庫的redo和undo來完成。隔離性由鎖得以實現。
1.持久性的保證:
-
Checkpoint技術:當每次數據頁臟了之后,立馬就將緩沖頁刷回磁盤,對性能影響太大,故MySQL一般會使用checkpoint技術。
checkpoint點的設置比較復雜,MySQL會綜合考慮redo log的大小,系統宕機之后的數據恢復時間、緩沖池的使用情況等等來取一個checkpoint,將臟頁刷到磁盤。
而一旦將數據刷到磁盤后,那么checkpoint之前的數據操作持久性就都得到保證了。 -
Write ahead log策略:
當事務提交時,先寫重做日志,再修改頁。
WAL 技術的基本思想是先將修改操作記錄到 redo log 中,再將數據寫入磁盤中。這樣可以確保在出現宕機等異常情況時,可以通過 redo log 中的信息將數據恢復到事務執行前的狀態,從而保證數據的一致性和持久性。
Redo log的持久化策略
show variables like ‘innodb_flush_log_at_trx_commit’;
0:表示事務提交時不將日志寫到磁盤,而是每次都把redo緩沖起來,僅僅在master thread中每一秒進行一次fsync
1:默認值為,表示每次事務提交時必須調用一次fsync將log寫到磁盤
2:每次事務提交時MySQL都會把log buffer的數據寫入log file,但是flush(刷到磁盤)操作并不會同時進行。MySQL會每秒執行一次 flush(刷到磁盤)操作
2.隔離性的實現
在執行SQL寫某一行的時候,是需要把要寫的行上加X鎖的,MySQL在默認設置下讀是不加鎖的快照讀
當前讀:讀取的是記錄的最新版本,讀取時需保證其他并發事務不能修改當前記錄,會對讀取的記錄進行加鎖,
對于我們的日常操作,如:select…lock in share mode(共享鎖),select…for update、update、insert、delete(排他鎖)都是當前讀。
快照讀:簡單的select(不加鎖)就是快照讀,快照讀記錄的是數據的可見版本,有可能是歷史數據,不加鎖,是非阻塞讀。
Read Committed:每次select 生成一個快照讀。
Repeatable Read:開啟事務后的第一個select語句才是快照讀的地方
Serializable:快照讀會退化為當前讀
undolog版本鏈
當insert操作時,產生的undo log日志只在回滾時需要,在事務提交后可立即刪除。
當update,delete操作時,產生的undo log日志不僅在回滾時需要,在快照讀時也需要,不會立即被刪除。
MVCC:MVCC 技術通過為每個事務保存一個可見的數據版本,來實現在并發訪問的情況下保證事務的隔離性。
MVCC 主要涉及以下兩個方面:
版本號:在 MVCC 中,每一行數據都會有多個版本號,每個版本號對應著一個事務,表示該版本是由該事務所修改的。
事務在進行修改時,會為該行數據生成一個新的版本,該版本號比當前最大的版本號大1。而查詢操作只能讀取版本號小于等于當前事務的版本號的數據。
事務版本鏈:每個事務都有一個版本鏈,版本鏈是由該事務創建的所有版本所組成的鏈表。
在該鏈表上,每個版本都指向前一個版本,最后一個版本指向 NULL。
版本鏈的作用是,當事務需要回滾時,可以沿著版本鏈將數據恢復到事務開始的狀態。
通過使用版本號和事務版本鏈,MVCC 實現了 InnoDB 存儲引擎的多版本并發控制,同時也保證了事務的隔離性。
在執行查詢操作時,根據當前事務的隔離級別,InnoDB 存儲引擎會選擇可見的數據版本。
在可重復讀的隔離級別下,InnoDB 存儲引擎會將當前事務的版本號作為可見的最大版本號,因此當前事務只能讀取該版本號之前的數據版本,避免了臟讀和不可重復讀等問題。
MVCC只在可重復對和RC隔離級別下生效,不同的是RR級別下在事務第一個select語句開始的時候生成快照讀視圖,RC級別下每次select都會生成新的讀視圖
3.原子性的實現
通過回滾操作保證原子性。回滾需要用到undo log來進行回滾。
當一個事務需要修改一行數據時,InnoDB 首先將該行數據的原始值拷貝到 undo log 中,然后執行修改操作。
如果事務需要回滾,可以使用 undo log 中的原始值將數據恢復到修改前的狀態。
如果事務提交,則可以將 undo log 中的信息刪除
undo log 通過保存數據的原始值來保證事務的原子性,可以使得數據修改操作能夠撤銷和回滾,并確保數據的一致性。
如果部分提交事務,可以參考Save point命令