目? 錄
一、概述
二、ACID 四大特性
三、MySQL 事務
?四、事務隔離級別
1.說明
2.現象
(1)臟讀
(2)不可重復讀
(3)幻讀
3.查看隔離級別
4.設置隔離級別
5.隔離級別
(1)初始化
(2)讀未提交
(3)讀提交
(4)可重復讀
(5)串行化
6.可重復讀的幻讀
(1)說明
(2)快照讀解決幻讀
(3)當前讀解決幻讀
(4)出現幻讀的兩種情況
一、概述
- 事務是一個最小的工作單元。數據庫中,事務表示一個完整的行為;
- 事務只針對 DML 語句;
- 使用事務機制后,在同一個事務當中,多條 DML 語句或同時執行成功,或同時執行失敗;
- 事務的經典實例:賬戶轉賬,或一方金額減少和一方金額增加同時成功,或同時失敗。
二、ACID 四大特性
- 原子性(Atomicity):事務包含的所有操作或全部成功,或全部失敗;
- 一致性(Consistency):事務開始前和完成后,數據應該是一致的;
- 隔離性(Isolation):多個用戶并發訪問數據庫時,數據庫為每一個用戶開啟的事務不能被其他事務的操作所干擾,多個并發事務之間要相互隔離;
- 持久性(Durability):一個事務一旦被提交,則對數據庫中的數據改變是永久的,即便是數據庫系統出現故障也不會丟失提交事務的操作。
三、MySQL 事務
- 開啟 MySQL 事務:【start transaction;】或【begin;】;
- 回滾事務:【rollback;】;
- 提交事務:【commit;】;
- 只要執行 rollback 或 commit,事務都會結束;
- MySQL 默認事務自動提交,執行一條 DML 語句提交一次。
?四、事務隔離級別
1.說明
隔離級別 | 現? 象 | ||
---|---|---|---|
臟讀 | 不可重復讀 | 幻讀 | |
讀未提交 (read uncommitted) | 存在 | 存在 | 存在 |
讀提交 (read committed) | 不存在 | 存在 | 存在 |
可重復讀 (repeatable read) | 不存在 | 不存在 | 存在 |
串行化 (serializable) | 不存在 | 不存在 | 不存在 |
- 隔離級別排序:讀未提交 < 讀提交 < 可重復讀 < 串行化;
- 不同隔離級別會存在不同現象,按照嚴重性排序:臟讀 > 不可重復讀 > 幻讀;
- Oracle 默認隔離級別是讀提交,MySQL 默認隔離級別是可重復讀。
2.現象
(1)臟讀
? ? ? ? 一個事務讀取了另一個事務尚未提交的數據。此時如果另一個事務回滾或修改了數據,那么讀取臟數據的事務就會出現數據處理錯誤。
(2)不可重復讀
????????一個事務內,多次讀取同一條數據,得到的結果可能不一致。因為其他事務可能對該數據做出修改操作。
(3)幻讀
????????事務執行過程中,前后幾次相同查詢條件得到的結果集不一致,可能更多也可能更少。
3.查看隔離級別
- 查看當前會話的隔離級別:【select @@transaction_isolation;】;
- 查看全局的隔離級別:【select @@global.transaction_isolation;】。
select @@global.transaction_isolation;select @@transaction_isolation;
4.設置隔離級別
- 設置當前會話的隔離級別:【set session transaction isolation level <隔離級別>】;
- 設置全局的隔離級別:【set global transaction isolation level <隔離級別>】。
# 設置全局隔離級別-- 讀未提交
set global transaction isolation leve read uncommitted;-- 讀提交
set global transaction isolation level read committed;-- 可重復讀
set global transaction isolation level repeatable read;-- 串行化
set global transaction isolation level serializable;# 設置會話隔離級別-- 讀未提交
set session transaction isolation leve read uncommitted;-- 讀提交
set session transaction isolation level read committed;-- 可重復讀
set session transaction isolation level repeatable read;-- 串行化
set session transaction isolation level serializable;
5.隔離級別
(1)初始化
? ? ? ? 事先準備好一個 users 表,表中有 id,name,gender 字段。
drop table if exists users;create table users(id int primary key auto_increment,name varchar(10),gender varchar(2) default '未知'
);insert into users(name, gender) values('黃梓婷', '女'),('趙聰', '男'),('呂不韋', '男');select * from users;
(2)讀未提交
- A 事務與 B 事務,A 事務可以讀到 B 事務未提交的數據;
- 這種級別的兩個事務之間幾乎沒有隔離,實際數據庫產品中,沒有默認該隔離級別的;
- 但前隔離級別,臟讀、不可重復讀、幻讀 現象都存在;
- 模擬實例:先將全局事務隔離級別設置為 read uncommitted。然后開啟兩個 dos 命令窗口分別登錄 MySQL 來模擬 A、B事務。
-- 1.設置全局隔離級別
set global transaction isolation level read uncommitted;-- 2.然后打開兩個 dos 命令窗口,分別登錄 MySQL
A 事務 | B 事務 |
---|---|
3. mysql> use test; | |
4. mysql> use test; | |
5. mysql> start transaction; | |
6. mysql> start transaction; | |
7. mysql> select * from users; | |
8.?mysql> insert into users(name, gender) values('陳子悅', '女'); | |
9.?mysql> select * from users; |
? ? ? ? ?可以看到,出現了臟讀,意味著三個現象都會出現。
(3)讀提交
- A 事務與 B 事務,A 事務可以讀取到 B 事務提交之后的數據;
- Oracle 默認是此隔離級別;
- 當前隔離級別,會出現不可重復讀和幻讀現象;
- 模擬實例:先將全局事務隔離級別設置為 read committed。然后開啟兩個 dos 命令窗口分別登錄 MySQL 來模擬 A、B事務。
-- 1.設置全局隔離級別
set global transaction isolation level read committed;-- 2.然后打開兩個 dos 命令窗口,分別登錄 MySQL
A 事務 | B 事務 |
---|---|
3. mysql> use test; | |
4. mysql> use test; | |
5. mysql> start transaction; | |
6. mysql> start transaction; | |
7. mysql> select * from users; | |
8.?mysql> insert into users(name, gender) values('陳子悅', '女'); | |
9.?mysql> select * from users; | |
9.?mysql> commit; | |
10.?mysql> select * from users; | |
11.?mysql> update users set name = '牛佳佳' where id = 2; | |
12.?mysql> select * from users; |
? ? ? ? 可以看到,出現了不可重復讀和幻讀。?
(4)可重復讀
- A 事務與 B 事務,A 事務開啟后讀取記錄,B 事務修改數據并提交,A 事務讀取到的還是修改前的記錄;
- MySQL 默認是此隔離級別;
- 模擬實例:先將全局事務隔離級別設置為 repeatable read。然后開啟兩個 dos 命令窗口分別登錄 MySQL 來模擬 A、B事務。
-- 1.設置全局隔離級別
set global transaction isolation level repeatable read;-- 2.然后打開兩個 dos 命令窗口,分別登錄 MySQL
A 事務 | B 事務 |
---|---|
3. mysql> use test; | |
4. mysql> use test; | |
5. mysql> start transaction; | |
6. mysql> start transaction; | |
7. mysql> select * from users; | |
8.?mysql> update users set name = '艾東東' where id = 2; | |
9.?mysql> select * from users; | |
10.?mysql> commit; | |
11.?mysql> select * from users; | |
12.?mysql> insert into users(name, gender) values('張弘毅', '男'); | |
13.?mysql> commit; | |
14.?mysql> select * from users; | |
15.?select * from users for update; |
# A 事務
mysql> use test;
Database changed
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> select * from users;
+----+--------+--------+
| id | name | gender |
+----+--------+--------+
| 1 | 黃梓婷 | 女 |
| 2 | 牛佳佳 | 男 |
| 3 | 呂不韋 | 男 |
| 5 | 陳子悅 | 女 |
+----+--------+--------+
4 rows in set (0.00 sec)mysql> select * from users;
+----+--------+--------+
| id | name | gender |
+----+--------+--------+
| 1 | 黃梓婷 | 女 |
| 2 | 牛佳佳 | 男 |
| 3 | 呂不韋 | 男 |
| 5 | 陳子悅 | 女 |
+----+--------+--------+
4 rows in set (0.00 sec)mysql> select * from users;
+----+--------+--------+
| id | name | gender |
+----+--------+--------+
| 1 | 黃梓婷 | 女 |
| 2 | 牛佳佳 | 男 |
| 3 | 呂不韋 | 男 |
| 5 | 陳子悅 | 女 |
+----+--------+--------+
4 rows in set (0.00 sec)mysql> select * from users;
+----+--------+--------+
| id | name | gender |
+----+--------+--------+
| 1 | 黃梓婷 | 女 |
| 2 | 牛佳佳 | 男 |
| 3 | 呂不韋 | 男 |
| 5 | 陳子悅 | 女 |
+----+--------+--------+
4 rows in set (0.00 sec)mysql> select * from users for update;
+----+--------+--------+
| id | name | gender |
+----+--------+--------+
| 1 | 黃梓婷 | 女 |
| 2 | 艾東東 | 男 |
| 3 | 呂不韋 | 男 |
| 5 | 陳子悅 | 女 |
| 6 | 張弘毅 | 男 |
+----+--------+--------+
5 rows in set (0.00 sec)
# B 事務
mysql> use test;
Database changed
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)mysql> update users set name = '艾東東' where id = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0mysql> commit;
Query OK, 0 rows affected (0.00 sec)mysql> insert into users(name, gender) values('張弘毅', '男');
Query OK, 1 row affected (0.02 sec)mysql> commit;
Query OK, 0 rows affected (0.00 sec)
? ? ? ? 可以看到 A 事務在使用【select * from users for update;】語句查詢時會出現幻讀,但是不加【for update】不會出現幻讀。也就是說,幻讀是可能會出現的,for update 是當前讀。
(5)串行化
- 此隔離級別,避免了所有的問題;
- 但是效率低,因為這種隔離級別會導致事務排隊處理,不支持并發;
- 模擬實例:先將全局事務隔離級別設置為 serializable。然后開啟兩個 dos 命令窗口分別登錄 MySQL 來模擬 A、B事務。
-- 1.設置全局隔離級別
set global transaction isolation level serializable;-- 2.然后打開兩個 dos 命令窗口,分別登錄 MySQL
A 事務 | B 事務 |
---|---|
3. mysql> use test; | |
4. mysql> use test; | |
5. mysql> start transaction; | |
6. mysql> start transaction; | |
7. mysql> select * from users; | |
8.?mysql> insert into users(name, gender) values('周子恩', '女'); | |
9. commit; |
? ? ? ? 在 B 事務執行完 insert 語句后,會等待 A 事務結束,否則 B 事務不會進入下一步。兩個事務是串行執行的。?
6.可重復讀的幻讀
(1)說明
- MySQL 默認隔離級別是可重復讀,但是不可以完全避免幻讀問題;
- 解決幻讀的方法:
- 快照讀:普通 select 語句都是快照讀。通過 MVCC(多版本并發控制) 方式解決幻讀,因為在可重復的隔離級別中,事務執行的查詢數據與該事務啟動時查詢的數據是一致的,即使中間有新插入的數據,也不會被查詢出來,所以避免了幻讀問題;
- 當前讀:帶有 for update 的 select 語句、insert 語句、alert 語句、delete 語句都是當前讀。通過 next-key lock(記錄鎖 + 間隙鎖)的方式解決幻讀,因為執行 select …… for update 語句時,會加上 next-key lock。若其他事務在 next-key lock 鎖范圍內插入了一條記錄,則該語句會被阻塞,無法成功插入,所以也避免了幻讀問題。
(2)快照讀解決幻讀
- 在整個事務處理過程中,執行相同的普通 select 語句,都是讀取快照;
- 快照是固定某個時刻的數據;
- 原理:底層由 MVCC 實現,開始事務后,執行第一個查詢語句后,會創建一個 read view,后續的查詢語句利用該 read view,通過該 read view 可以在 undo log 版本鏈中找到事務開始時的數據,所以事務過程中每次查詢到的數據是一致的。
(3)當前讀解決幻讀
- 每一次都讀取最新數據;
- 事務開始,會對查詢范圍內的數據加鎖,不允許其他事務對該范圍內的數據進行增、刪、改。即該 select 語句范圍內的數據不允許并發,只能排隊執行。next-key lock,就是 間隙鎖 + 記錄鎖,間隙鎖用來保證鎖定范圍內不允許執行 insert 操作,記錄鎖保證鎖定范圍不允許執行 update 和 delete 操作。
(4)出現幻讀的兩種情況
?A 事務與 B 事務,在 A 事務中第一次查詢使用快照讀,B 事務插入數據。然后在 A 事務中第二次查詢使用當前讀,則會產生幻讀現象;
A 事務與 B 事務,在 A 事務中第一次查詢使用快照讀,在 B 事務中插入數據。然后在 A 事務中更新 B 事務插入的那條記錄。最后在 A 事務中再次使用快照讀,則會產生幻讀現象。(因為執行 update 操作,會在底層執行一次當前讀)。