MySQL事務的隔離級別與併發問題

白菜1031發表於2021-11-01
MySQL版本:8.0.27

一、事務併發執行面臨的問題

1. 髒讀(Dirty Read)

如果事務A讀到了未提交的事務B修改過的資料,就意味著發生了髒讀現象。

2. 不可重複讀(Non-Repeatable Read)

如果事務B修改了未提交的事務A讀取到的資料,就意味著發生了不可重複讀現象。

3. 幻讀(Phantom Read)

事務A先根據某個範圍條件查詢出了一些記錄,而事務B寫入了一些符合該條件的新記錄,當事務A再次以相同的條件查詢時,查詢到了新的記錄,就意味著發生了幻讀現象。

二、SQL標準中的四種隔離級別

1. READ UNCOMMITTED(未提交讀)

READ UNCOMMITTED 級別,事務中的修改,即使沒有提交,對其他事務也都是可見的。
也就是說該隔離級別會出現髒讀問題。

2. READ COMMITTED(已提交讀)

READ COMMITTED 解決了髒讀問題,它滿足事務隔離性的簡單定義:一個事務從開始直到提交之前,所做的任何修改對其他事務都是不可見的。

3. REPEATABLE READ(可重複讀)

REPEATABLE READ 解決了髒讀不可重複讀的問題。該級別保證了在同一個事務中多次讀取同樣記錄的結果是一致的。但是理論上,可重複讀隔離級別還是無法解決幻讀(Phantom Read)問題。
InnoDB 儲存引擎通過 MVCC(多版本併發控制)和 Next-Key (臨鍵鎖) 很大程度上避免了幻讀問題。
可重複讀是MySQL的預設事務隔離級別。

4. SERIALIZABLE(序列化)

SERIALIZABLE 通過強制事務序列執行,避免了髒讀不可重複讀幻讀的問題。SERIALIZABLE 會在讀取的每一行資料上都加鎖,所以可能導致大量的超時和鎖爭用問題。

三、四種隔離級別對比

隔離級別髒讀可能性不可重複讀可能性幻讀可能性加鎖讀
READ UNCOMMITTED×
READ COMMITTED××
REPEATABLE READ×××
SERIALIZABLE×××

四、四種隔離級別事務並行示例

檢視事務的隔離級別
SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
設定事務的隔離級別
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL <level>;

level 有4個可選值:

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ
  • SERIALIZABLE

修改全域性隔離級別需要退出會話重新連線MySQL生效。

初始資料
CREATE TABLE `user`  (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` varchar(10) NOT NULL COMMENT '姓名',
  `gender` char(1) NOT NULL COMMENT '性別',
  `age` tinyint(3) UNSIGNED NOT NULL COMMENT '年齡',
  `phone` char(11) NOT NULL COMMENT '電話',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `idx_username`(`name`) USING BTREE,
  UNIQUE INDEX `unq_phone`(`phone`) USING BTREE
) ENGINE=InnoDB CHARACTER SET=utf8mb4;
INSERT INTO `user` VALUES (10, 'M小明', '男', 16, '11111111111');
INSERT INTO `user` VALUES (20, 'H小紅', '女', 15, '22222222222');
INSERT INTO `user` VALUES (30, 'L小麗', '女', 18, '33333333333');
INSERT INTO `user` VALUES (40, 'M小梅', '女', 21, '44444444444');
INSERT INTO `user` VALUES (50, 'L小亮', '男', 20, '55555555555');

1. READ UNCOMMITTED(未提交讀)

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | READ-UNCOMMITTED |
+-----------------------+-----------------+
  • 髒讀
事務A事務B
begin;begin;
SELECT * FROM user WHERE id=10;-
-UPDATE user SET age=11 WHERE id=10;
SELECT * FROM user WHERE id=10;
讀取到了事務B未提交的修改(age=11),出現髒讀
-
-rollback;
SELECT * FROM user WHERE id=10;
讀取到的資料又變回了(age=10)
-
commit;-

2. READ COMMITTED(已提交讀)

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+-----------------+
  • 不可重複讀
事務A事務B
begin;begin;
SELECT * FROM user WHERE id=10;-
-UPDATE user SET age=12 WHERE id=10;
SELECT * FROM user WHERE id=10;
讀取不到事務B未提交的修改(age=10),沒有出現髒讀
-
-commit;
SELECT * FROM user WHERE id=10;
讀取到了事務B已提交的修改(age=12),出現不可重複讀
-
commit;

3. REPEATABLE READ(可重複讀)

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
  • 可重複讀
事務A事務B
begin;begin;
SELECT * FROM user WHERE id=10;-
-UPDATE user SET age=13 WHERE id=10;
SELECT * FROM user WHERE id=10;
讀取不到事務B未提交的修改(age=12),沒有出現髒讀
-
-commit;
SELECT * FROM user WHERE id=10;
讀取不到事務B已提交的修改(age=12),沒有出現不可重複讀
-
commit;
  • 幻讀

由於 MySQL 的 InnoDB 儲存引擎通過 MVCC(多版本併發控制)和 Next-Key (臨鍵鎖) 很大程度上避免了幻讀問題,所以無法演示大部分幻讀現象,但是InnoDB 儲存引擎並不能完全禁止幻讀。

事務A事務B
begin;begin;
SELECT * FROM user WHERE id>30;-
-INSERT INTO user VALUES(60, 'J小靜', '女', 10, '66666666666');
-commit;
SELECT * FROM user WHERE id>30;
讀取不到事務B插入的記錄,沒有出現幻讀
-
UPDATE user SET age=11 WHERE id=60;-
SELECT * FROM user WHERE id>30;
讀取到了事務B插入的記錄,出現了幻讀
-
commit;

4. SERIALIZABLE(序列化)

SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | SERIALIZABLE |
+-----------------------+-----------------+

SERIALIZABLE 通過強制事務序列執行,避免了髒讀不可重複讀幻讀的問題。

  • 當事務A讀取一條記錄時,這條記錄會被加上讀鎖(共享鎖),其他事務可以查詢這一條記錄,但是無法修改
事務A事務B
begin;
SELECT * FROM user WHERE id=10;-
-SELECT * FROM user WHERE id=10;
執行成功
-UPDATE user SET age=14 WHERE id=10;
阻塞
  • 當事務A修改一條記錄時,這條記錄會被加上寫鎖(排它鎖),其他事務都無法查詢和修改這一條記錄
事務A事務B
begin;-
UPDATE user SET age=14 WHERE id=10;-
-SELECT * FROM user WHERE id=10;
阻塞
commit;-
-執行完成
  • 當事務A讀取範圍記錄時,該範圍都會被加上讀鎖(共享鎖),其他事務無法在該範圍內新增、修改記錄,也無法將範圍外的記錄修改為符合範圍條件的記錄。
事務A事務B
begin;-
SELECT * FROM user WHERE id>30;-
-INSERT INTO user VALUES(60, 'J小靜', '女', 10, '66666666666');
阻塞
commit;-
-執行完成
事務A事務B
begin;
SELECT * FROM user WHERE id>30;-
-UPDATE user SET id=31 WHERE id=10;
阻塞
commit;-
-執行完成

相關文章