1、MySQL 隔離界別檢視
- 檢視回話隔離級別
SELECT @@session.tx_isolation;
SELECT @@tx_isolation;
複製程式碼
- 檢視系統隔離級別
SELECT @@global.tx_isolation;
複製程式碼
2、MySQL 隔離級別修改
- MySQL 預設的隔離級別是可重複讀( REPEATABLE READ)
- 在 my.inf 檔案中修改隔離級別
transaction-isolation = {READ-UNCOMMITTED | READ-COMMITTED | REPEATABLE-READ | SERIALIZABLE}
複製程式碼
- 使用者可以用SET TRANSACTION語句改變單個會話或者所有新進連線的隔離級別。語法如下:
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
複製程式碼
3、MySQL 四種隔離級別
隔離級別 | 髒讀(Dirty Read) | 不可重複讀(NonRepeatable Read) | 幻讀(Phantom Read) |
---|---|---|---|
未提交讀(Read uncommitted) | 可能 | 可能 | 可能 |
已提交讀(Read committed) | 不可能 | 可能 | 可能 |
可重複讀(Repeatable read) | 不可能 | 不可能 | 可能 |
可序列化(Serializable ) | 不可能 | 不可能 | 不可能 |
3.1、未提交讀
- 允許髒讀,也就是說一個事務有可能讀到另一個事務未提交的資料
3.2、已提交讀
- 只能讀到已經提交的資料,Oracle等多數資料庫的預設隔離級別
3.3、可重複讀
- 存在幻讀
3.4、可序列化
- 完全序列化,每次讀都需要獲得表級共享鎖,讀寫阻塞
4、例項操作
- 新建一個表用來測試
CREATE TABLE `test` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(50) NOT NULL DEFAULT '' COMMENT '名字',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='測試';
複製程式碼
4.1、髒讀
- 當一個事務訪問一個資料,並且進行了修改。另一個事務讀到了被修改的資料,並且使用了這個資料。
- sessoin1 (插入資料但不提交事務)
mysql> SELECT @@session.tx_isolation; // 查詢會話隔離級別可重複讀
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ |
+------------------------+
1 row in set, 1 warning (0.04 sec)
mysql> SELECT @@tx_isolation; //查詢系統隔離級別為可重複讀
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
mysql> start transaction; //開啟事務
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test(`name`) values("qiu"); //插入資料成功,此時事務還沒有提交
Query OK, 1 row affected (0.01 sec)
mysql> select * from test;
+----+------+
| id | name |
+----+------+
| 1 | qiu |
+----+------+
1 row in set (0.00 sec)
複製程式碼
- sessoin2(可重複讀,證明不會出現髒讀)
mysql> SELECT @@session.tx_isolation; //會話隔離級別為可重複讀
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@tx_isolation; //系統隔離級別為可重複讀
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
mysql> select * from test; //查詢不到 sessoin1 未提交的資料,不會出現髒讀現象
Empty set (0.00 sec)
複製程式碼
- sessoin3(為提交讀出現髒讀現象)
mysql> SELECT @@session.tx_isolation;//會話隔離級別為未提交讀
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED | --------讀到了 session1 未提交的資料,出現髒讀現象
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select * from test;//讀到了 session1 未提交的資料,此為髒讀
+----+------+
| id | name |
+----+------+
| 1 | qiu |
+----+------+
1 row in set (0.00 sec)
複製程式碼
4.2、不可重複讀
-
在同一個事務內,多次讀取同一個資料,此時事務還沒有完成。另一個事務在前一個事務兩次讀取之間修改了資料,由於修改了資料,前一個事務讀到的資料不一樣,因此稱為不可重複讀。
-
sessoin1(事務內第一次讀)
mysql> SELECT @@session.tx_isolation; //隔離級別為提交讀
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-COMMITTED |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> start transaction; //開啟事務
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test; //開啟事務內的第一次查詢
+----+------+
| id | name |
+----+------+
| 2 | qiu |
+----+------+
1 row in set (0.01 sec)
複製程式碼
- sessoin2
mysql> SELECT @@session.tx_isolation;//隔離級別為可重複讀
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> start transaction; //開啟事務
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test;
+----+------+
| id | name |
+----+------+
| 2 | qiu |
+----+------+
1 row in set (0.01 sec)
mysql> insert into test(`name`) values ("hello"); //在sessoin1第一次查詢後修改了資料
Query OK, 1 row affected (0.01 sec)
mysql> select * from test;
+----+-------+
| id | name |
+----+-------+
| 2 | qiu |
| 3 | hello |
+----+-------+
2 rows in set (0.00 sec)
mysql> commit; //提交事務
Query OK, 0 rows affected (0.01 sec)
複製程式碼
- sessoin1(事務內第二次讀)
mysql> select * from test; //在事務內第二次讀,讀到了 sessoin2 提交的資料
+----+-------+
| id | name |
+----+-------+
| 2 | qiu | ---------------READ-COMMITTED級別出現不可重複讀現象
| 3 | hello |
+----+-------+
2 rows in set (0.00 sec)
複製程式碼
4.3、可重複讀
- 驗證 REPEATABLE-READ 級別下的可重複讀
- sessoin1(事務內第一次讀)
mysql> SELECT @@session.tx_isolation;//隔離級別為可重複讀
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> start transaction; //開啟事務
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test;
+----+-------+
| id | name |
+----+-------+
| 2 | qiu |
| 3 | hello |
+----+-------+
2 rows in set (0.00 sec)
複製程式碼
- sessoin2
mysql> SELECT @@session.tx_isolation; //隔離級別為可重複讀
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test;
+----+-------+
| id | name |
+----+-------+
| 2 | qiu |
| 3 | hello |
+----+-------+
2 rows in set (0.00 sec)
mysql> insert into test (`name`) values ("hi"); //sessoin1 第一次讀之後改變資料
Query OK, 1 row affected (0.01 sec)
mysql> select * from test;
+----+-------+
| id | name |
+----+-------+
| 2 | qiu |
| 3 | hello |
| 4 | hi |
+----+-------+
3 rows in set (0.00 sec)
mysql> commit; //提交事務
Query OK, 0 rows affected (0.00 sec)
複製程式碼
- sessoin1(事務內第二次讀)
mysql> select * from test;
+----+-------+
| id | name |
+----+-------+
| 2 | qiu |
| 3 | hello | -------------sessoin1 沒有讀到 sessoin2 提交的資料,出現可重複讀現象
+----+-------+
2 rows in set (0.00 sec)
複製程式碼
4.4、幻讀
-
第一個事務對錶中的所有資料進行修改,第二個事務往表裡面插入一條資料。此時第一個事務發現表中還有未修改的資料,好像出現了幻覺一樣。
-
幻讀現象1:
session1: session2:
mysql> select @@global.tx_isolation, @@tx_isolation;
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation |
+-----------------------+-----------------+
| REPEATABLE-READ | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set, 2 warnings (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test;
Empty set (0.00 sec)
mysql> insert into test (`id`, `name`) values (1, "hi~~~");
Query OK, 1 row affected (0.00 sec)
mysql> select * from test;
Empty set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from test;
Empty set (0.00 sec)
mysql> insert into test (`id`, `name`) values (1, "hello");
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
//what fuck ???剛剛查詢,告訴我沒有資料。等我插入的時候就告訴我主鍵衝突了。此乃幻讀現象
複製程式碼
- 幻讀現象2:
session1: session2:
mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from test;
+----+-------+
| id | name |
+----+-------+
| 1 | hi~~~ |
+----+-------+
1 row in set (0.00 sec)
mysql> insert into test (`id`, `name`) values (2, "hello~~");
Query OK, 1 row affected (0.01 sec)
mysql> select * from test;
+----+-------+
| id | name |
+----+-------+
| 1 | hi~~~ |
+----+-------+
1 row in set (0.04 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test;
+----+-------+
| id | name |
+----+-------+
| 1 | hi~~~ |
+----+-------+
1 row in set (0.00 sec)
mysql> update test set name = "up";
Query OK, 2 rows affected (0.01 sec)
Rows matched: 2 Changed: 2 Warnings: 0
//what fuck ???剛出查詢不是隻有一條資料嗎?怎麼更新了兩條。此乃幻讀現象
mysql> select * from test;
+----+------+
| id | name |
+----+------+
| 1 | up |
| 2 | up |
+----+------+
2 rows in set (0.00 sec)
複製程式碼
- innodb_locks_unsafe_for_binlog:設定InnoDB是否在搜尋和索引掃描中使用間隙鎖(gap locking)
- 當隔離級別是可重複讀,且禁用innodb_locks_unsafe_for_binlog的情況下,在搜尋和掃描index的時候使用的next-key locks可以避免幻讀。
4.5、加鎖
- 通過加鎖來防止幻讀
session1: session2:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
//加鎖鎖住了 id <= 1 的範圍
mysql> select * from test where id <= 1 for update;
+----+------+
| id | name |
+----+------+
| 1 | up |
+----+------+
1 row in set (0.18 sec)
//id 不在鎖內,允許插入
mysql> insert into test (`id`, `name`) values (3, "lock");
Query OK, 1 row affected (0.15 sec)
mysql> select * from test;
+----+------+
| id | name |
+----+------+
| 1 | up |
| 2 | up |
+----+------+
2 rows in set (0.01 sec)
//id = 1 已經加了寫鎖,事務等待鎖釋放
mysql> insert into test(`id`, `name`) values (1, "lock");
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> select * from test;
+----+------+
| id | name |
+----+------+
| 1 | up |
| 2 | up |
+----+------+
2 rows in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test;
+----+------+
| id | name |
+----+------+
| 1 | up |
| 2 | up |
+----+------+
2 rows in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test;
+----+------+
| id | name |
+----+------+
| 1 | up |
| 2 | up |
| 3 | lock | ------------session2 插入的資料
+----+------+
4 rows in set (0.00 sec)
複製程式碼
- 通過加鎖讀來獲得其他事務提交的結果
session1: session2:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test;
+----+------+
| id | name |
+----+------+
| 1 | up |
+----+------+
4 rows in set (0.01 sec)
mysql> insert into test (`id`, `name`) values (7, "hello");
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.08 sec)
mysql> select * from test;
+----+------+
| id | name |
+----+------+
| 1 | up |
+----+------+
4 rows in set (0.00 sec)
mysql> select * from test;
+----+------+
| id | name |
+----+------+
| 1 | up |
+----+------+
4 rows in set (0.00 sec)
//讀到了 session2 提交的資料
mysql> select * from test lock in share mode;
+----+-------+
| id | name |
+----+-------+
| 1 | up |
| 7 | hello |
+----+-------+
5 rows in set (0.00 sec)
//讀到了 session2 提交的資料
mysql> select * from test for update;
+----+-------+
| id | name |
+----+-------+
| 1 | up |
| 7 | hello |
+----+-------+
5 rows in set (0.00 sec)
//讀不到 session2 提交的資料
mysql> select * from test;
+----+------+
| id | name |
+----+------+
| 1 | up |
+----+------+
4 rows in set (0.00 sec)
複製程式碼
- 可重複讀和提交讀本身就是互相矛盾的。保證了可重複讀,就讀不到其他事務的提交;保證了提交讀,兩次讀取的資料可能會出現不一致。
- MySQL 預設的隔離級別是可重複讀,可通過加鎖讀來獲取其他事務的提交。
- MySQL 的可重複讀並不能避免幻讀,可通過加 Next-Key Lock 來避免幻讀現象。
- Next-Key Lock:鎖定一個範圍,包括記錄本身。
總結
- 每種資料庫隔離級別都解決了一個問題。資料庫隔離級別依次增強,效能也依次變差。大部分環境中使用 READ-COMMITTED 是可行的。
參考文獻
關注公眾號
- 大家可以關注我的公眾號【學霸的一天】,更多有趣、有用的知識等你來發現