目錄
1. 什么是 MVCC
2. MVCC 是否徹底解決了事物的隔離性
3. MySQL 中如何實現共享鎖和排他鎖
4. MySQL 中如何實現悲觀鎖和樂觀鎖
1. 什么是 MVCC
????????MVCC(Multi-Version Concurrency Control,多版本并發控制)是一種多版本并發控制機制,它通過給每一個操作加上一個版本號,來解決它的隔離性問題!(主要是用來解決幻讀問題)
????????它使用了快照讀的方式,將歷史版本的結果集保存到緩存中(ReadView),那么后續需要查詢結果集的時候,就不會進行實時的查詢(當前讀),而是從歷史版本中找到與當前查詢操作所對應的版本號匹配的結果集。(版本號是全局共享的)
????????MVCC 雖然主要用來解決幻讀問題的,它也是可以解決不可重復讀問題的,并且 MVCC 是 MySQL 內置的一個機制,先來看看 MVCC 是如何解決不可重復讀問題的:
數據庫中原數據:
客戶端 1?執行如下操作:
// 1.開啟事物 A [版本 1]
begin; // 2.查詢 user 表中 id 為 3 的用戶身份信息 [版本 1]
select * from user where id = 3; // 3.等事物 B 執行修改操作后,再次查詢 id 為 3 的用戶 [取緩存中的版本 1]
select * from user where id = 3;
客戶端 2 執行如下操作:
// 開啟事物 [版本 1]
begin;// 等事物 A 執行完第一次查詢操作時,將 id 為 3 的用戶名改為王老五 [版本 2]
update user set name = '王老五' where id= 3;// 提交事物
commit;// 此時可以再開一個客戶端 3,驗證一下數據庫中是否已經是最新數據
?經過上述兩步操作之后,可以得出以下結果 >>
- 客戶端 1:因為客戶端 1 只執行了開啟事務,并沒有提交事物,所以客戶端 1 第二次查詢得到的數據行的 name 依然是 王五。
- 客戶端 3?:因為客戶端 2 執行了修改操作,并提交事物,那么客戶端 3 查詢得到的數據行的 name 就變成了 王老五。
MVCC 的核心思想:
????????MVCC 將每個事物的讀和寫操作進行解耦,通過保存數據的歷史版本來實現并發控制。每個事物在開始的時候會創建一個讀視圖(ReadView),用于確定在事物開始時可見的數據版本。在 MVCC 中,當一個事物執行寫操作時,會生成一個新的數據版本,并將舊版本的數據保存在回滾日志(Undo Log)中。這樣其他事物在讀取數據時,仍然可以訪問到舊版本的數據,從而避免了幻讀問題和不可重復讀問題。
(歷史版本的數據就類似于對之前查詢出來的結果集拍張照,在當前事物還沒執行完,還需要查詢時,直接從緩存中拿那個版本的照片)
2. MVCC 是否徹底解決了事物的隔離性
????????在面試中如果被問到這個問題了,那么回答沒有徹底解決。雖然 RR 隔離級別中通過 MVCC 解決大部分幻讀問題,但是依然存在幻讀問題。
請看示例,原數據:
?客戶端 1 執行如下操作:
// 事物 A
begin;select * from user where id > 0 and id < 9;// 等待事物 B 執行完新增操作后,使用下面兩種方式進行查詢
select * from user where id > 0 and id < 9; [快照讀]select * from user where id > 0 and id < 9 for update; [當前讀]
客戶端 2 執行如下操作:
begin;// 事物 B 執行新增操作
insert into user(id,name,age,address) values(4,'老六',66,'廣州');commit;
最終執行結果:
- 事物 A 使用快照讀(讀緩存)的方式,查詢結果集仍然是 3 條,(MVCC機制保護)
- 事物 A?使用當前讀(實時讀)的方式,查詢結果集變成了 4 條。(存在幻讀問題)
【解決方案】既然 MVCC 也不能解決幻讀問題,那么該怎么解決幻讀問題 ??
- 方案一:加鎖 + RR隔離級別
- 方案二:使用串行化隔離級別
MySQL 中如何加鎖,使用 for update?(既是當前讀,也是加鎖),示例:
select * from user where id > 0 and id < 9 for update; // 排他鎖(寫鎖)
????????當事物 A 在執行時,進行鎖表操作,那么事物 B 開啟事物后,想要執行新增操作,但是事物 A 已經執行鎖表操作了,所以事物 B 嘗試獲取鎖,發現獲取不到,一段時間后就放棄了,所以顯示獲取鎖超時的錯誤(默認超時時間為 50s)。
3. MySQL 中如何實現共享鎖和排他鎖
共享鎖也稱為讀鎖,它允許多個事物同時獲取同一數據的共享鎖,用于讀數據。
語法:? select... lock in share mode
select * from user wehre id = 1 lock in share mode;
排他鎖也稱為寫鎖,它只允許一個事物獲取排他鎖,用于修改數據。
語法:?select .... for update
select * from user where id = 1 for update;
4. MySQL 中如何實現悲觀鎖和樂觀鎖
1. 悲觀鎖
悲觀鎖就是通過加上 for update 進行鎖表的操作實現的
2. 樂觀鎖
MySQL 沒有內置樂觀鎖的實現,需要在業務代碼中通過手動指定版本號來實現。