1. 程式人生 > 實用技巧 >如何理解MySQL事務的隔離級別

如何理解MySQL事務的隔離級別

關注我,更多精彩文章第一時間推送給你

  1. 讀未提交(READ UNCOMMITTED)
  2. 讀已提交(READ COMMITTED)
  3. 可重複讀(REPEATABLE READ)
  4. 可序列化(SERIALIZABLE)
  • MySQL的預設事務的隔離級別是可重複讀
-- 登入mysql的root賬戶,-p待輸入密碼,-h mysql伺服器地址 -P 埠號(注意大寫)
➜ mysql -u root -p -h localhost -P 3307
Enter password: ****
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 284
Server version: 5.7.28 MySQL Community Server (GPL)

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>
-- 檢視當前事務的隔離級別
mysql> show variables like 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+

-- 或者如此檢視事務的隔離級別
mysql> SELECT @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ         |
+-------------------------+

-- 可以看到MySQL的預設事務隔離級別是可重複讀

讀未提交

會產生所有的問題(髒讀、不可重複讀、幻讀)

讀已提交

在此事務隔離級別下,事務B只能在事務A修改並且已提交後才能讀取到事務A修改的資料。

避免了髒讀

但還是會產生(不可重複讀、幻讀)

可重複讀

在此事務隔離級別下,事務B只能在事務A修改資料並提交後,自己也提交事務後,才能讀取到事務A修改的資料。

避免了髒讀和不可重複讀

但還是會產生幻讀問題

可序列化

此種事務隔離級別別最高,不會發生任何以下問題(髒讀、可重複讀、幻讀),通過加鎖實現

讀鎖和寫鎖,只在讀讀不阻塞,讀寫、寫讀、寫寫都會阻塞。

解讀一下併發造成的問題(髒讀、不可重複讀、幻讀)

資料庫表t_user我的表字段

mysql> show columns from t_user;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(11)     | NO   | PRI | NULL    | auto_increment |
| name  | varchar(50) | NO   |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+

注意:MySQL 預設開啟事務自動提交模式,即除非顯式的開啟事務(BEGIN 或 START TRANSACTION),否則每條 SOL 語句都會被當做一個單獨的事務自動執行。

-- 可以看到mysql預設開啟自動提交模式
mysql> SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+

-- 可以設定是否自動提交
-- 值為 0 和值為 OFF:關閉事務自動提交。
-- 值為 1 和值為 ON:開啟事務自動提交。
-- 如下可設定的4種值
-- SET autocommit = 0|1|ON|OFF;
  • 髒讀

事務A 讀取到了 事務B 修改但是未提交的髒資料

開始之前一定調整事務的隔離級別為讀未提交,不然不可能產生髒讀現象

mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-UNCOMMITTED        |
+-------------------------+

首先檢視一下t_user表的資料

mysql> select * from t_user;
+----+--------+
| id | name   |
+----+--------+
|  1 | 達摩   |
+----+--------+

然後進行如下測試

-- session1 事務A 開始
mysql> begin;
-- session2 事務B 開始
mysql> begin;
-- session2 事務B 修改name = 呂布  (注意這裡並未提交)
mysql> update t_user set name = '呂布' where id = 1;
-- session1 事務A 讀取id = 1的資料 (注意,讀到了未提交的髒資料 name = 呂布)
mysql> select * from t_user where id = 1;
+----+--------+
| id | name   |
+----+--------+
|  1 | 呂布   |
+----+--------+
-- session1 事務A 對讀到的髒資料進行提交(事務A結束)
mysql> commit;
-- session2 事務B 對剛才的修改進行回滾(事務B結束)
mysql> rollback;

-- 最終資料庫中的name並沒有改變,但是事務A讀到了髒資料,這就是髒讀
-- 此事務隔離級別(讀未提交)也會造成不可重複讀和幻讀,這裡不做演示,看下面的隔離級別中演示

  • 不可重複讀

事務A 讀取表的名字為達摩(並未提交),之後事務B修改了名字為曹操(修改完提交了),此時事務A在未提交的情況下又讀取了name卻變成了曹操,這就是不可重複讀

設定事務的隔離級別為讀已提交,也可以為讀未提交,因為讀未提交也會產生不可重複讀現象

-- 設定事務的隔離級別為讀已提交
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)
-- 檢視修改結果,已經為讀已提交
mysql> SELECT @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-COMMITTED          |
+-------------------------+

首先檢視一下t_user表的資料

mysql> select * from t_user;
+----+--------+
| id | name   |
+----+--------+
|  1 | 達摩   |
+----+--------+

然後進行如下測試

-- session1 事務A開始事務
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
-- session1 事務A 讀取表中資料(注意此時未提交事務)
mysql> select * from t_user where id = 1;
+----+--------+
| id | name   |
+----+--------+
|  1 | 達摩   |
+----+--------+
-- session2 事務B直接修改並提交事務
mysql> update t_user set name = '曹操' where id = 1;
Query OK, 1 row affected (0.03 sec)
-- session1 事務A 再次讀取表中資料,發現同一個事務中得到了不一樣的結果,這就是不可重複讀
mysql> select * from t_user where id = 1;
+----+--------+
| id | name   |
+----+--------+
|  1 | 曹操   |
+----+--------+
-- 只要是事務A一直不提交,其他事務修改提交之後,事務A再次查詢都能查到最新修改的結果
-- 此事務隔離級別也會造成幻讀,但是避免了髒讀
-- 測試是否避免髒讀,事務A此時未提交
-- session2 事務B開始
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
-- session2 事務B修改name = 伽羅 (注意此時未提交)
mysql> update t_user set name = '伽羅' where id = 1;
Query OK, 1 row affected (0.00 sec)
-- session1 事務A進行讀取,看到並沒有讀取到髒資料
mysql> select * from t_user where id = 1;
+----+--------+
| id | name   |
+----+--------+
|  1 | 曹操   |
+----+--------+
-- session2 事務B回滾
mysql> rollback;
Query OK, 0 rows affected (0.05 sec)
-- session1 事務A提交
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
  • 知識點:為什麼加了寫鎖,別的事務還可以進行讀操作?

  • 因為InnoDB有MVCC機制(多版本併發控制),可以使用快照讀,而不會被阻塞。

  • 幻讀

事務A讀取表中資料為一條(這時候未提交), 事務B向表中添加了一條記錄(提交),此時事務A再次查詢表中資料,結果是兩條,好像產生了幻覺,這就是幻讀

設定事務的隔離級別為可重複讀,也可以為讀未提交,也可以為讀已提交,因為這三種事務隔離級別都會產生幻讀現象

-- 設定事務的隔離級別為可重複讀
mysql> SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Query OK, 0 rows affected (0.00 sec)
-- 檢視修改結果,已經為可重複讀
mysql> SELECT @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ         |
+-------------------------+

首先檢視一下t_user表的資料

mysql> select * from t_user;
+----+--------+
| id | name   |
+----+--------+
|  1 | 曹操   |
+----+--------+

之後進行的測試

-- session1 事務A開啟
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
-- session1 事務A查詢(注意未提交)
mysql> select * from t_user;
+----+--------+
| id | name   |
+----+--------+
|  1 | 曹操   |
+----+--------+
-- session2 事務B新增資料(提交)
mysql> insert into t_user(name) values('馬可波羅');
Query OK, 1 row affected (0.04 sec)
-- session1 事務A再次查詢(一個事務中兩次查詢多出了資料,幻讀)
mysql> select * from t_user;
+----+--------------+
| id | name         |
+----+--------------+
|  1 | 曹操         |
|  2 | 馬可波羅      |
+----+--------------+
-- 測試此事務隔離級別能避免不可重複讀現象(此時事務A還是未提交)
-- session2 事務B修改id= 1的資料名字為狄仁傑(提交)
mysql> update t_user set  name = '狄仁傑' where id = 1;
Query OK, 1 row affected (0.04 sec)
-- 此時session1 事務A再次讀取資料(可以看到跟上一次一樣,證明避免了不可重複讀現象)
mysql> select * from t_user;
+----+--------------+
| id | name         |
+----+--------------+
|  1 | 曹操         |
|  2 | 馬可波羅      |
+----+--------------+

以上講的都是併發操作可能造成的讀的問題

關於樂觀鎖和悲觀鎖解決併發寫操作可能造成的丟失更新的問題請訪問我的這篇文章