特性 | 特點 |
---|---|
Atomicity(原子性) | 事務是不可分割的,其對數據的修改,要么全都執行,要么全都不執行 |
Consistency(一致性) | 在事務提交的前后的狀態和數據都必須是一致的 |
Isolation(隔離性) | 在多事務并發時,保證事務不受并發操作影響的"獨立"環境執行,這就意味著事務處理過程中的中間狀態對外部是不可見的,反之亦然 |
Druability(持久性) | 指事務一旦提交,數據就持久化保存到磁盤中不會丟失 |
問題 | 現象 | 描述 |
---|---|---|
臟讀 | A事務正在對一條記錄做修改,在A事務完成并提交前,這條記錄的數據就處于不一致的狀態(有可能回滾也有可能提交),與此同時,B事務也來讀取同一條記錄,如果不加控制,B事務讀取了這些"臟"數據,并據此作進一步處理,就會產生未提交的數據以來關系 | 一個事務中讀取到另一個事務尚未提交的數據,不符合一致性要求 |
不可重復讀 | 一個事務在讀取某些數據后的某個時間,再次讀取以前讀過的數據,卻發現其讀出的數據已經發生了改變或某些記錄已經被刪除了 | 一個事務中多次讀取的數據不一致,原因是收到其他事務已提交update的干擾,不符合隔離性 |
幻讀 | 一個事務按相同的查詢條件重新讀取以前查詢過的數據,卻發現其他事務插入滿足其查詢條件的新數據 | 一個事務中多次讀取的數據不一致,原因是受其他事務已提交insert/delete的干擾,不符合隔離性 |
臟讀、不可重復讀和幻讀,其實都是MySQL讀一致性問題,必須由數據庫提供一定的事務隔離機制來解決.
隔離級別 | 臟讀 | 不可重復讀 | 幻讀 |
---|---|---|---|
Read uncommitted(讀未提交) | √ | √ | √ |
Read committed(讀已提交) | × | √ | √ |
Repetatble read(可重復讀)(MySQL默認) | × | × | √ |
Serializable(串行化) | × | × | × |
查看當前數據庫的事務隔離級別:show variables like ‘tx_isolation';
設置事務隔離級別:set tx_isolation='隔離級別'
mysql版本:5.7.34
涉及表:
兩個MySQL客戶端
客戶端A ===================> 客戶端B(下面每張圖片兩個客戶端皆以第一張圖命名為準
1.1 設置事務隔離級別set tx_isolation=‘read-uncommitted';
1.2 客戶端A和客戶端B各開啟一個事務,
1.3 客戶端A只做查詢,客戶端B對id = 1的記錄做修改;
1.4 再兩個事務都未提交的情況下,事務A讀到了事務B修改后的數據
1.5 一旦客戶端B的事務因為某種原因rollback,那么客戶端A查詢到的數據其實就是臟數據,不符合一致性的要求
2.1 設置隔離級別讀已提交:set tx_isolation=‘read-committed';
2.2 客戶端A和客戶端B各開啟一個事務,
2.3 客戶端A只做查詢,客戶端B對id = 1的記錄做修改;
2.4 客戶端B未提交事務時,客戶端A不能查詢客戶端B未提交的數據,解決了臟讀的問題
2.5 當客戶端B提交事務后,客戶端A再次對表進行查詢,結果與上一步不一致,即產生了不可重復讀的問題,不符合隔離性
3.1 設置隔離級別可重復讀:set tx_isolation=‘repeatable-read';
3.2 客戶端A和客戶端B各開啟一個事務,
3.3 客戶端B修改表中數據然后提交;
3.4 客戶端A查詢表中數據,并未出現與上一步不一致的問題,解決了不可重復讀的問題
3.5 在客戶端A中執行update account set balance = balance - 100 where id = 1;blance并未有變成800-100=700;而是使用客戶端B提交后的數據來算的,所以是600;數據的一致性并沒有被破壞;可重復讀的隔離級別下使用的是MVCC機制,select操作不會更新版本號,是快照讀(歷史版本),保證同一事務下的可重復讀;insert/update/delete會更新版本號,是當前讀(當前版本)保證數據的一致性
3.6 客戶端B重新開啟一個事務插入一條數據后提交
3.7 在客戶端A中重新查詢表數據,并沒有出現客戶端B剛才新增的數據,沒有出現幻讀
3.8 驗證幻讀:在客戶端A中,對id = 4 的數據做修改;可以更新成功;再次進行查詢就能查詢出客戶端B新增的數據,出現幻讀問題,不符合隔離性
4.1 設置隔離級別串行化:set tx_isolation=‘serializable';
4.2 客戶端A和客戶端B各開啟一個事務,
4.3 客戶端A先查詢表中id = 1的數據
4.4 在客戶端A事務未提交時,客戶端B對表中id = 1 的數據做更新;由于客戶端A的事務并沒有提交,客戶端B的更新動作將會阻塞至到客戶端A提交事務或者超時,超時SQL報錯:Lock wait timeout exceeded; try restarting transaction
4.5 在客戶端B中更新id = 2 的數據卻可以成功,說明在串行化的隔離級別下,innodb的查詢也會被加上行鎖;
4.6 如果客戶端A執行的是一個范圍查詢,那么該范圍內的所有行包括每行記錄所在的間隙區間范圍(就算該行未被插入也會加鎖,這種是間隙鎖)都會被加鎖,此時如果客戶端B對該范圍內的數據做任何操作都會被阻塞;所以就避免了幻讀;
4.7 串行化這種隔離級別并發性極低,所以再真實的開發很少會遇到,這也是MySQL為什么使用可重復讀作為默認的隔離級別的重要原因
MySQL默認的隔離級別是可重復讀,可是還是會出現幻讀問題;間隙鎖再某種情況下可以解決幻讀問題;
概述:間隙鎖,鎖的就是兩個值之間的空隙.
假設表中數據如下:
那么間隙就有(4,10)、(10,15)和(15,正無窮)三個間隙;
1.1 設置隔離級別可重復讀:set tx_isolation=‘repeatable-read';
1.2 客戶端A和客戶端B各開啟一個事務,
1.3 在客戶端A執行update account set balance = 1000 where id > 5 and id 13 ;
1.4 在客戶端A未提交的時候,客戶端B是沒有辦法對這個范圍包含的所有行記錄(包括間隙行記錄)以及行記錄所在間隙里執行insert/update操作,即4id=15這個區間內都無法修改數據,id = 15 同樣不能修改;
1.5 間隙鎖只有在可重復讀的隔離級別下才會生效
概述:臨建鎖是行鎖和間隙鎖的結合,想上面那個4id=15就屬于臨建鎖;
無索引行鎖會升級成為表鎖
3.1 客戶端A和客戶端B各開啟一個事務,
3.2 在客戶端A執行update account set balance = 1000 where name = ‘李四';
3.3 在客戶端A未提交的時候,客戶端B執行update account set balance = 800 where id = 15 ;同樣會被阻塞至客戶端A提交或者超時;
3.4 MySQL中的鎖主要是加載索引字段上,如果使用再非索引字段上,行鎖會升級成表鎖;
4.1 客戶端A和客戶端B各開啟一個事務,
4.2 在客戶端A執行select * from account where id = 1 for update ;
4.3 在客戶端A未提交的時候,客戶端B執行update account set balance = 800 where id = 1 ;會被阻塞至客戶端A提交或者超時;
結論:Innodb引擎實現了行鎖,雖然行鎖機制實現方面所帶來的性能損耗可能比表級鎖定會更高,但是再整體并發處理能力肯定要強于表級鎖;當系統并發量高的時候,行級鎖和表級鎖相比就會有比較明顯的優勢;但是行級鎖使用起來也比表級鎖復雜,當我們使用不當的時候,可能會使行鎖的性能不僅不比表級鎖的性能高,甚至可能會更差.
為什么行鎖鎖定的粒度小,開銷反而會比表級鎖的開銷大?
因為表級鎖只需要找到當前表就可以進行加鎖,行鎖的話需要對表中記錄進行掃描,直至掃描到需要加鎖的行才可以進行加鎖,所以行鎖的開銷是比表級鎖的開銷要來得大的.
真實開發情況下對鎖優化的一些建議:
到此這篇關于MySQL隔離級別和鎖機制的文章就介紹到這了,更多相關MySQL隔離級別和鎖機制內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持腳本之家!