MySQL的事務性也是其重要特性之一。
什么是事務:事務的本質是并發控制的單元,是用戶定義的一個操作序列。這些操作要么都做,要么都不做,是 一個不可分割的工作單位。
目的:事務的目的在于將數據庫從一種一致性狀態轉換為另一種一致性狀態,保證系統始終處于一個完整且正確的狀態。
組成:事務可由一條非常簡單的 SQL 語句組成,也可以由一組復雜的 SQL 語句組成。
特征:在數據庫提交事務時,可以確保要么所有修改都已經保存,要么所有修改都不保存; 事務是訪問并更新數據庫各種數據項的一個程序執行單元。
事務控制語句:
-- 顯示開啟事務
START TRANSACTION | BEGIN-- 提交事務,并使得已對數據庫做的所有修改持久化
COMMIT-- 回滾事務,結束用戶的事務,并撤銷正在進行的所有未提交的修改
ROLLBACK-- 創建一個保存點,一個事務可以有多個保存點
SAVEPOINT identifier-- 刪除一個保存點
RELEASE SAVEPOINT identifier-- 事務回滾到保存點
ROLLBACK TO [SAVEPOINT] identifier
一、事務的ACID特性
1. ACID 四大特性
特性 | 描述 | 作用 |
---|---|---|
A(原子性,Atomicity) | 事務是不可分割的最小工作單元,要么全部執行成功,要么全部回滾。 | 確保事務中所有 SQL 語句要么都成功,要么都失敗。 |
C(一致性,Consistency) | 事務執行前后,數據庫必須保持一致狀態,不會破壞數據的完整性。 | 保證數據在事務開始和結束后滿足所有約束,如外鍵、唯一性等。 |
I(隔離性,Isolation) | 多個事務同時執行時,彼此不會相互影響,避免臟讀、幻讀等問題。 | MySQL 通過不同的隔離級別控制事務之間的可見性。 |
D(持久性,Durability) | 事務一旦提交,數據就會永久存儲,即使系統崩潰也不會丟失。 | 通過 redo log 和 binlog 保證事務的持久性。 |
2. ACID 詳解
2.1 原子性(Atomicity)
? 事務必須是不可分割的整體:
- 如果某個操作失敗,事務會回滾,撤銷所有已執行的操作。
- MySQL 通過
UNDO LOG
(回滾日志)實現回滾機制。
? 示例
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
ROLLBACK; -- 撤銷所有操作
🔹 如果發生錯誤或斷電,所有已執行的 SQL 語句都會被回滾,保證賬戶數據不丟失。
2.2 一致性(Consistency)
? 事務執行前后,數據必須滿足完整性約束:
- 事務不能破壞數據庫的外鍵約束、唯一性約束等規則。
? 示例 假設 accounts
表規定 balance >= 0
,那么:
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
🔹 如果某個賬戶余額變成負數,事務必須回滾,保持數據一致性。
2.3 隔離性(Isolation)
? 多個事務并發執行時,彼此的修改互不影響:
- 解決臟讀、不可重復讀、幻讀等問題。
- MySQL 提供 四種事務隔離級別:
- READ UNCOMMITTED(讀未提交) → 允許讀取未提交數據,可能導致臟讀。
- READ COMMITTED(讀已提交) → 只能讀取已提交數據,但可能出現不可重復讀。
- REPEATABLE READ(可重復讀,MySQL 默認) → 多次查詢結果一致,避免不可重復讀。
- SERIALIZABLE(可串行化) → 最高隔離級別,強制事務串行執行,性能最低。
? 示例
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- 第一次讀取
-- 其他事務修改 balance
SELECT balance FROM accounts WHERE id = 1; -- 仍然是原來的值
COMMIT;
🔹 可重復讀(默認)能保證第二次查詢仍然返回原始數據,避免不可重復讀問題。
2.4 持久性(Durability)
? 事務一旦提交,數據就必須永久存儲:
- MySQL 通過 Redo Log(重做日志) 和 Binlog(二進制日志) 記錄已提交的事務。
- 即使服務器崩潰,重啟后 MySQL 也能恢復數據。
? 示例
START TRANSACTION;
UPDATE orders SET status = 'shipped' WHERE id = 123;
COMMIT; -- 事務提交后數據不會丟失
🔹 即使 MySQL 崩潰,訂單狀態仍然是 shipped
。
二、四大隔離級別詳解
1. MySQL 四大事務隔離級別
MySQL 提供四種 事務隔離級別(由低到高):
隔離級別 | 臟讀 | 不可重復讀 | 幻讀 | 性能 | SQL 設置 |
---|---|---|---|---|---|
READ UNCOMMITTED(讀未提交) | ? 可能 | ? 可能 | ? 可能 | ? 性能最高 | SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; |
READ COMMITTED(讀已提交) | ? 不可能 | ? 可能 | ? 可能 | ? 適中 | SET TRANSACTION ISOLATION LEVEL READ COMMITTED; |
REPEATABLE READ(可重復讀)(MySQL 默認) | ? 不可能 | ? 不可能 | ? 可能 | ? 適中 | SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; |
SERIALIZABLE(可串行化) | ? 不可能 | ? 不可能 | ? 不可能 | ? 性能最低 | SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; |
? = 解決了該問題
? = 可能出現該問題
2. 事務隔離級別問題詳解
2.1 臟讀(Dirty Read)
定義:一個事務可以讀取到另一個未提交事務的修改。如果該事務回滾,則讀取到的數據是無效的,造成數據不一致。
示例
- 事務 A 修改
balance = 1000 → 500
(但未提交)。 - 事務 B 讀取
balance = 500
(但事務 A 可能回滾)。 - 事務 A 回滾,
balance
變回1000
,但事務 B 讀到了錯誤的數據。
? 解決方法:
- READ COMMITTED 及以上級別可以避免臟讀。
2.2 不可重復讀(Non-Repeatable Read)
定義:同一事務內多次查詢同一條記錄,但結果不一致,因為另一個事務修改并提交了數據。
示例
- 事務 A 讀取
balance = 1000
。 - 事務 B 修改并提交
balance = 500
。 - 事務 A 再次讀取
balance
,發現變成500
,數據發生變化。
? 解決方法:
- REPEATABLE READ 及以上級別可以避免不可重復讀。
2.3 幻讀(Phantom Read)
定義:一個事務內多次查詢相同條件的數據,但結果不一致,因為另一個事務插入/刪除了數據。
示例
- 事務 A 運行
SELECT * FROM users WHERE age > 30;
,返回5
條數據。 - 事務 B 插入 一條
age > 30
的數據并提交。 - 事務 A 再次運行 相同查詢,返回
6
條數據,發生幻讀。
? 解決方法:
- SERIALIZABLE 級別可以避免幻讀(使用表級鎖)。
- InnoDB 默認通過 GAP 鎖避免幻讀。
3. 事務隔離級別總結
隔離級別 | 適用場景 | 優缺點 |
---|---|---|
READ UNCOMMITTED(讀未提交) | 允許最高并發,但數據不安全 | 可能發生臟讀、不可重復讀、幻讀 |
READ COMMITTED(讀已提交) | 大部分數據庫的默認級別(如 Oracle),保證讀到已提交數據 | 可能發生不可重復讀、幻讀 |
REPEATABLE READ(可重復讀)(MySQL 默認) | 適合大多數應用,保證同一事務多次讀取結果一致 | 可能發生幻讀 |
SERIALIZABLE(可串行化) | 最高級別,數據最安全,所有事務串行執行 | 性能最低,適用于高安全性應用 |
三、MVCC詳解
1. 什么是 MVCC(多版本并發控制)?
MVCC(Multi-Version Concurrency Control,多版本并發控制)是一種并發控制機制,可以讓多個事務無鎖并發執行,提高數據庫性能,同時避免臟讀、不可重復讀等問題。
- MVCC 主要用于 InnoDB 存儲引擎,默認支持
REPEATABLE READ
隔離級別。 - 通過 數據行的多個版本,使得讀操作不會被寫操作阻塞,提升并發性能。
2. MVCC 解決了哪些問題?
? 避免臟讀:事務只能讀取已經提交的數據。
? 避免不可重復讀:在同一事務內,多次讀取相同數據,結果保持一致。
? 提高并發性能:讀操作無需加鎖,避免鎖競爭。
? 無法解決幻讀:MVCC 無法防止插入數據帶來的幻讀,需要使用 GAP 鎖。
3. MVCC 的實現原理
3.1 MVCC 依賴的關鍵機制
MVCC 在 InnoDB 中主要依賴以下 三個重要的存儲結構:
- 隱藏列(隱式事務 ID 和回滾指針)
- Undo Log(回滾日志)
- Read View(讀視圖)
3.2 MVCC 關鍵實現細節
(1)隱藏列
InnoDB 為每個數據行額外存儲兩個隱藏列:
trx_id
:記錄插入/修改該行的事務 ID。roll_pointer
:指向 Undo Log,可通過回滾日志獲取數據的舊版本。
(2)Undo Log(回滾日志)
- 作用:存儲舊版本數據,使得歷史版本可讀(多版本存儲)。
- 實現:
- 每次
UPDATE
或DELETE
,都會把舊值存入 Undo Log,并更新roll_pointer
指向舊版本數據。 - 事務回滾時,Undo Log 可用于恢復舊數據。
- 每次
(3)Read View(讀視圖)
- 作用:控制事務可見性,決定事務是否可以看到某個版本的數據。
- 核心邏輯:
- 事務
T1
開啟時,會生成 Read View(包含當前活躍事務的trx_id
列表)。 - 查詢數據時:
trx_id
小于 Read View 最小活躍事務 ? → 可見(數據已提交)。trx_id
在活躍事務范圍內 ? → 不可見(數據未提交)。trx_id
大于當前事務 ID ? → 不可見(數據由新事務插入)。
- 事務
T1
只能看到 Read View 創建時的數據快照,即快照讀。
- 事務
4. MVCC 讀寫方式
(1)快照讀(Snapshot Read)
- 讀取的是數據的歷史版本,不加鎖,提高查詢性能。
- 適用 SQL:
- 實現方式:
- Read View 讀取符合當前事務可見性的數據。
- Undo Log 提供歷史版本數據。
(2)當前讀(Current Read)
- 讀取的是最新版本數據,會加鎖。
- 適用 SQL(需要保證數據一致性):
- 實現方式:
- 事務需要讀取最新版本數據,并阻塞其他事務修改該數據。
5. MVCC 在不同隔離級別下的行為
隔離級別 | 快照讀 | 當前讀 |
---|---|---|
READ UNCOMMITTED(讀未提交) | 讀取未提交的數據(不安全) | 直接讀取最新版本 |
READ COMMITTED(讀已提交) | 讀取最新已提交版本 | 加鎖,讀取最新數據 |
REPEATABLE READ(可重復讀,MySQL 默認) | 同一事務內,多次查詢結果一致 | 加鎖,讀取最新數據 |
SERIALIZABLE(可串行化) | 強制事務串行執行 | 加鎖,阻塞其他事務 |
6. MVCC 適用范圍
? 適用于 InnoDB 表(默認支持 MVCC)。
? 不適用于 MyISAM(MyISAM 不支持事務,不會存儲多個數據版本)。
? 不適用于 SERIALIZABLE
級別(MVCC 失效,會強制加鎖)。
7. MVCC 總結
機制 | 作用 |
---|---|
隱藏列(trx_id、roll_pointer) | 記錄事務 ID、舊數據指針 |
Undo Log(回滾日志) | 存儲數據的歷史版本 |
Read View(讀視圖) | 確定事務可見性,控制快照讀 |
MVCC 優勢
? 讀操作不加鎖,并發性能高。
? 保證事務隔離性,避免臟讀、不可重復讀問題。
MVCC 局限
? 需要存儲多個版本的數據,占用空間。
? 無法避免幻讀(需要GAP 鎖)。
適用場景
? OLTP(高并發事務處理)場景,如 銀行系統、電商訂單管理。
? 查詢頻繁但更新少的系統,如 分析報表。
MVCC 是 MySQL 高性能事務控制的核心,合理利用 MVCC 機制,可以大幅提升數據庫性能,同時保證數據一致性!
四、MySQL中鎖機制
?
MySQL 的鎖機制用于控制多個事務并發訪問數據庫時的數據一致性和并發性。不同的存儲引擎(如 InnoDB 和 MyISAM)實現的鎖機制有所不同,InnoDB 采用更細粒度的鎖控制,并支持 行級鎖、表級鎖 和 意向鎖 等。
1. MySQL 鎖的分類
MySQL 的鎖可分為 全局鎖、表級鎖 和 行級鎖,其中行級鎖又包括共享鎖、排他鎖、意向鎖、間隙鎖等。
鎖類型 | 適用范圍 | 特點 |
---|---|---|
全局鎖 | 作用于整個數據庫 | FLUSH TABLES WITH READ LOCK ,適用于備份數據,影響所有操作。 |
表級鎖 | 作用于整張表 | LOCK TABLES ,如 MyISAM 存儲引擎使用表鎖,事務并發能力低。 |
行級鎖 | 作用于單行數據 | InnoDB 支持,提供更高的并發性能,但需要管理更多的鎖信息。 |
2. MySQL 表級鎖
2.1 表鎖(Table Lock)
適用于 MyISAM,每次鎖定整個表,不支持行級鎖。
操作 | SQL 語句 | 特性 |
---|---|---|
加讀鎖(共享鎖) | LOCK TABLES users READ; | 其他線程可讀,但不可寫 |
加寫鎖(排他鎖) | LOCK TABLES users WRITE; | 其他線程不可讀也不可寫 |
釋放表鎖 | UNLOCK TABLES; | 釋放鎖,其他事務可訪問 |
? 缺點:影響并發,大多數情況下不推薦使用。
3. MySQL 行級鎖
3.1 共享鎖(S 鎖,Shared Lock)
- 多個事務可同時讀取數據,但不能修改數據。
- 適用于
SELECT ... LOCK IN SHARE MODE
。
🔹 應用場景:如果多個事務需要并發讀取數據,并且不希望數據被修改,可使用共享鎖。?
3.2 排他鎖(X 鎖,Exclusive Lock)
- 排他性強,其他事務不能讀取或修改被鎖定的行。
- 適用于
SELECT ... FOR UPDATE
。
🔹 應用場景:用于更新前的查詢,確保數據在更新前不會被其他事務修改。?
3.3 意向鎖(Intent Lock)
- 自動加鎖,用于標識事務希望獲取行級鎖,防止表鎖和行鎖沖突。
- InnoDB 自動管理,開發者無需手動控制。
鎖類型 | 作用 |
---|---|
意向共享鎖(IS,Intent Shared) | 事務想要加 S 鎖,表級鎖不能加 X 鎖 |
意向排他鎖(IX,Intent Exclusive) | 事務想要加 X 鎖,表級鎖不能加 S/X 鎖 |
🔹 應用場景:防止表級鎖與行級鎖沖突,提高鎖管理效率。
3.4 間隙鎖(Gap Lock,防止幻讀)
作用:防止幻讀,用于 REPEATABLE READ
隔離級別。
🔹 應用場景:當事務查詢 age BETWEEN 18 AND 25
時,即使不存在數據,InnoDB 仍會鎖住范圍,防止其他事務插入 age=20
的新數據。
3.5 Next-Key Lock(間隙鎖 + 行鎖)
- 鎖定一行數據及其范圍,防止新數據插入。
- InnoDB 在
REPEATABLE READ
級別下默認使用,避免幻讀。
🔹 如果 id=10
存在,InnoDB 不僅鎖定該行,還會鎖定 id=10
之后的索引范圍**,防止插入新記錄。
4. 死鎖(Deadlock)及避免方法
4.1 什么是死鎖?
死鎖是指兩個或多個事務互相等待對方釋放鎖,導致事務無法繼續執行。
4.2 如何避免死鎖?
方法 | 策略 |
---|---|
固定訪問順序 | 保證所有事務按照相同順序訪問資源,避免循環等待。 |
減少鎖的持有時間 | 及時提交事務,避免長時間持有鎖。 |
使用較高的隔離級別 | SERIALIZABLE 可以減少并發,避免復雜死鎖。 |
索引優化 | 減少鎖定的行數,優化 WHERE 條件,避免鎖住大量數據。 |
總結
鎖類型 | 作用 | 適用存儲引擎 |
---|---|---|
全局鎖 | 鎖住整個數據庫,影響所有操作 | 所有存儲引擎 |
表級鎖 | 鎖住整張表,適用于 MyISAM | MyISAM,InnoDB |
行級鎖 | 只鎖住特定行,提高并發性能 | InnoDB |
共享鎖(S 鎖) | 允許讀取,不允許修改 | InnoDB |
排他鎖(X 鎖) | 其他事務不能讀也不能寫 | InnoDB |
意向鎖(IS/IX) | 防止表級鎖和行級鎖沖突 | InnoDB |
間隙鎖(Gap Lock) | 防止幻讀,鎖定范圍 | InnoDB |
Next-Key Lock | 行鎖 + 間隙鎖,避免幻讀 | InnoDB |
?