快照讀和當前讀
????????在 MySQL 中,數據讀取方式主要分為?快照讀?和?當前讀,二者的核心區別在于是否依賴 MVCC(多版本并發控制)的歷史版本、是否加鎖,以及讀取的數據版本是否為最新。以下是詳細說明:
一、快照讀(Snapshot Read)
定義
????????快照讀是指讀取?MVCC 機制保存的歷史數據版本,通過事務啟動時生成的一致性視圖(Read View)獲取數據,不加鎖,因此不會阻塞其他事務的讀寫操作。
觸發場景
所有?不加鎖的普通 SELECT 語句?均為快照讀,例如:
SELECT * FROM 表 WHERE id = 1;
SELECT name FROM 表 WHERE status = 'active';
核心特點
- 依賴隔離級別:
- 在?讀已提交(Read Committed)?級別:每次 SELECT 都會生成新的 Read View,只能看到已提交的最新數據(避免臟讀)。
- 在?可重復讀(Repeatable Read)?級別:整個事務內使用同一個 Read View,多次讀取結果一致(避免不可重復讀)。
- 無鎖阻塞:讀取時不申請任何鎖,不影響其他事務的修改。
- 不讀取未提交數據:只能看到符合隔離級別的歷史提交版本,看不到其他事務未提交的修改。
二、當前讀(Current Read)
定義
????????當前讀是指讀取數據的?最新版本(無視 MVCC 歷史版本),且讀取時會對目標行?加鎖(共享鎖或排他鎖),以保證數據修改的原子性和一致性。
觸發場景
所有?需要獲取最新數據并加鎖的操作?均為當前讀,包括:
寫操作:
INSERT
、UPDATE
、DELETE
- 執行這些操作時,會先讀取最新數據版本,然后對目標行加?排他鎖(X 鎖),防止其他事務同時修改。
- 例如:
UPDATE 表 SET name = 'b' WHERE id = 1;
?會先當前讀?id=1
?的最新行,加 X 鎖后修改。加鎖的 SELECT 語句:
SELECT ... FOR SHARE
(或?SELECT ... LOCK IN SHARE MODE
):加?共享鎖(S 鎖),允許其他事務讀,但阻止其他事務加排他鎖。SELECT ... FOR UPDATE
:加?排他鎖(X 鎖),阻止其他事務加共享鎖或排他鎖。核心特點
- 讀取最新版本:直接讀取當前內存中已提交或未提交的最新數據(即使其他事務未提交,也能看到其修改)。
- 加鎖阻塞:會對目標行加鎖,其他事務若需操作同一行,需等待鎖釋放(提交或回滾)。
- 保證數據一致性:常用于需要精確修改最新數據的場景(如并發更新),避免丟失更新。
三、其他特殊讀取情況?
除了快照讀和當前讀,MySQL 中沒有其他獨立的讀取方式,但需注意以下特殊場景:
串行化(Serializable)隔離級別下的讀
- 在最高隔離級別 “串行化” 中,普通?
SELECT
?會被隱式轉換為?當前讀(加共享鎖),以完全避免并發問題。此時快照讀機制不生效,本質仍是當前讀的一種特殊表現。? ? ? ? ? ? ? ? 也就是說在串行化隔離級別下,線程 B 會等待線程 A 提交或回滾后,讀取到線程 A 提交后的數據或回滾前的原始數據。A沒提交或沒回滾時,線程B的讀取被阻塞
DDL 操作中的讀
- 執行?
ALTER TABLE
?等 DDL 語句時,會對表加 metadata 鎖(MDL 鎖),此時的讀操作可能被阻塞,但讀取方式仍屬于快照讀或當前讀(取決于具體語句是否加鎖)。總結:核心區別與場景表
讀取方式 觸發場景 是否加鎖 讀取的數據版本 典型操作示例 快照讀 不加鎖的普通 SELECT 不加鎖 MVCC 歷史提交版本 SELECT * FROM 表;
當前讀 寫操作(INSERT/UPDATE/DELETE)、加鎖 SELECT 加鎖(S 或 X 鎖) 最新版本(含未提交) UPDATE 表 SET ...;
、SELECT ... FOR UPDATE;
MySQL 可重復讀隔離級別下的事務鎖與數據可見性
1. 核心場景
- 初始數據:表中某行?
name = 'a'
。- 線程 A:開啟事務,執行?
UPDATE 表 SET name = 'b' WHERE name = 'a'
(未提交)。- 線程 B:開啟事務,執行?
UPDATE 表 SET name = 'c' WHERE name = 'b'
。
- 如果線程B是
UPDATE 表 SET name = 'c' WHERE name = 'a',因為是當前讀,所以name已經被線程A修改,所以找不到WHERE name = 'a',也就無事發生
2. 關鍵機制
當前讀(Current Read)
UPDATE
/DELETE
/INSERT
/SELECT ... FOR UPDATE
?會觸發當前讀,直接讀取最新數據(無論是否提交)。- 與快照讀的區別:普通?
SELECT
?使用事務啟動時的一致性視圖(MVCC),當前讀無視視圖,讀取最新版本。排他鎖(X 鎖)
UPDATE
?會對匹配的行加排他鎖,阻止其他事務同時修改。- 鎖持有至事務提交 / 回滾。
3. 執行流程
線程 A
- 觸發當前讀,匹配?
name = 'a'
?的行,加排他鎖,將其修改為?name = 'b'
(未提交)。- 鎖未釋放,數據僅存在于內存中。
線程 B
- 觸發當前讀,讀取到線程 A 未提交的?
name = 'b'
,匹配?WHERE name = 'b'
。- 嘗試加排他鎖,因線程 A 已持有鎖而進入阻塞狀態,等待鎖釋放。
4. 結果取決于線程 A 的操作
情況分類 線程 A 操作 線程 B 操作流程 最終? name
?值核心原因分析 情況 1 先提交 1. 阻塞結束,獲取鎖
2. 當前讀匹配?name = 'b'
,修改為?'c'
3. 提交事務? ?'c'
線程 A 的修改生效,線程 B 基于? 'b'
?進一步修改情況 2 先回滾 1. 阻塞結束,獲取鎖
2. 當前讀發現?name = 'a'
,條件不匹配,無修改
3. 提交事務? 'a'
線程 A 的修改撤銷,線程 B 的? WHERE
?條件不成立,未執行修改情況 3(不可能發生) 未提交 / 未回滾/(線程b先提交) 因未獲取鎖處于阻塞狀態, UPDATE
?未執行,無法提交事務? ? ?無結果 線程 B 需等待線程 A 釋放鎖才能執行,無法搶先提交 5. 關鍵點總結
當前讀的可見性
- 線程 B 的?
UPDATE
?能看到線程 A 未提交的修改(name = 'b'
),因此匹配條件并嘗試加鎖。鎖的阻塞機制
- 線程 B 必須等待線程 A 釋放鎖,無法搶先提交事務。
事務原子性
- 線程 A 的提交 / 回滾決定最終數據的基礎版本,線程 B 的修改依賴于此。
對比場景:若線程 B 查詢?
SELECT * FROM 表 WHERE name = 'b'
- 普通?
SELECT
(快照讀):看不到線程 A 未提交的修改,返回空結果。SELECT ... FOR UPDATE
(當前讀):會阻塞等待線程 A 釋放鎖,鎖釋放后返回?name = 'b'
(若 A 提交)或空(若 A 回滾)。6. 實踐建議
- 避免長事務:長時間持有鎖會增加阻塞概率。
- 優化查詢條件:確保?
UPDATE
?的?WHERE
?條件使用索引,減少鎖范圍。- 處理阻塞異常:業務代碼需考慮鎖等待超時的情況(如設置?
innodb_lock_wait_timeout
)。