MySQL事務與隔離級別:解析臟讀、不可重復讀和幻讀問題
今日目標
- 理解事務特性
- 理解事務隔離級別
- 理解不同事務下出現的問題臟讀、不可重復讀和幻讀
MySQL是廣泛使用的關系型數據庫管理系統(tǒng),它支持事務處理,確保數據操作的一致性和可靠性。本文將介紹MySQL事務的基本概念和重要性。
1. 事務簡介
事務:是一組操作的集合,它是一個不可分割的工作單位,事務會把所有的操作作為一個整體一起向系統(tǒng)提交或撤銷操作請求,即這些操作要么同時成功,要么同時失敗。
我們現在就拿一個經典的銀行存取錢例子來說明: 李四給王五轉賬500塊錢,李四銀行賬戶就要減少500,并且王五賬戶要增加500。這一組操作就必須在一個事務范圍內,要么轉賬同時成功,要么轉賬同時失敗
id | name | money |
1 | 李四 | 2000 |
2 | 王五 | 2000 |
轉賬分為以下情況:
1.正常情況:轉賬成功,可以分為以下基本三步完成,完成之后李四減少500,王五增加500,轉賬成功:
圖片
最終數據庫結果:
id | name | money |
1 | 李四 | 1500 |
2 | 王五 | 2500 |
2.異常情況:轉賬失敗,耶斯分為三步完成,假設李四減少500塊錢以后,王五賬戶金額沒有發(fā)生變化,這就造成了嚴重的數據不一致問題。
圖片
問題解決方式:通過事務完成,我們在執(zhí)行業(yè)務邏輯之前開啟事務,業(yè)務執(zhí)行完畢后,關閉事務。如果執(zhí)行過程中出錯,則事務回滾,將數據恢復到事務開啟之前狀態(tài)。
圖片
注意: 默認MySQL的事務是自動提交的,也就是說,當執(zhí)行完一條DML語句時,MySQL會立即隱 式的提交事務。
如果您覺得本文不錯,歡迎關注,點贊,收藏支持,您的關注是我堅持的動力!
2. 事務操作
通過sql語句,實現剛才的例子。
2.1. 數據準備
-- 創(chuàng)建數據庫test
create database if not exists test;
use test;
-- 刪除表
drop table if exists tb_account;
create table tb_account(
id int primary key AUTO_INCREMENT comment 'ID',
name varchar(10) comment '姓名',
money double(10,2) comment '余額'
) comment '賬戶表';
insert into tb_account(name, money) VALUES ('李四',2000), ('王五',2000);
2.2. 正常轉賬情況
-- 正常轉賬情況
-- 1. 查詢李四余額
select * from tb_account where name = '李四';
-- 2. 李四的余額減少500
update tb_account set money = money - 500 where name = '李四';
-- 3. 王五的余額增加500
update tb_account set money = money + 500 where name = '王五';
-- 4. 查看賬戶結果
select * from tb_account;
測試完畢之后檢查數據的狀態(tài), 可以看到數據操作前后是一致的。
圖片
2.3. 正常異常情況
-- 轉賬異常情況
-- 1. 查詢李四余額
select * from tb_account where name = '李四';
-- 2. 李四的余額減少500
update tb_account set money = money - 500 where name = '李四';
出錯了....
-- 3. 王五的余額增加500
update tb_account set money = money + 500 where name = '王五';
-- 4. 查看賬戶結果
select * from tb_account;
我們把數據都恢復到2000, 然后再次一次性執(zhí)行上述的SQL語句(出錯了.... 這句話不符合SQL語 法,執(zhí)行就會報錯),檢查最終的數據情況, 發(fā)現數據在操作前后不一致了。
圖片
2.3. 控制事務解決轉賬情況
1.開啟事務
-- 1.開啟事務
start transaction 或者 BEGIN;
2.提交事務
-- 2.提交事務
commit;
3.事務回滾
-- 3.事務回滾
rollback ;
轉賬案例
-- 開啟事務
start transaction;
-- 1. 查詢李四余額
select * from tb_account where name = '李四';
-- 2. 李四的余額減少1000
update tb_account set money = money - 500 where name = '李四';
-- 如果轉賬失敗 執(zhí)行rollback
-- 3. 王五的余額增加1000
update tb_account set money = money + 500 where name = '王五';
-- 如果正常執(zhí)行完畢, 則提交事務
commit;
-- 如果執(zhí)行過程中報錯, 則回滾事務
-- rollback;
3. 事務四大特性
MySQL事務遵循ACID屬性,即原子性、一致性、隔離性和持久性。
- 原子性(Atomicity):事務是不可分割的最小操作單元,事務內的所有操作要么全部成功,要么全部失敗。如果出現錯誤,所有更改都會被撤銷,數據庫保持一致狀態(tài)。
- 一致性(Consistency):事務執(zhí)行后,數據庫狀態(tài)必須滿足預定義的一致性狀態(tài),不會導致矛盾或不一致的數據。
- 隔離性(Isolation):隔離性控制多個事務之間的相互影響。MySQL支持不同的隔離級別,以滿足不同應用的需求。
- 持久性(Durability):事務一旦提交或回滾,它對數據庫中的數據的改變就是永久的,即使發(fā)生系統(tǒng)故障也不會丟失。
4. 事務隔離級別
SQL 標準中定義了四種隔離級別,分別是:
- 讀未提交(Read Uncommitted):這是最低的隔離級別。在該級別下,一個事務可以讀取另一個事務尚未提交的數據,可能導致臟讀(讀取到未提交數據)、不可重復讀(同一查詢多次執(zhí)行結果不一致)和幻讀(查詢結果集發(fā)生變化) 等問題。這個級別的并發(fā)性最高,但數據完整性較差。
- 讀已提交(Read Committed):在該級別下,事務只能讀取已提交的數據,避免了臟讀,但仍可能遇到不可重復讀和幻讀。這是許多數據庫系統(tǒng)的默認隔離級別,提供了更好的數據一致性。
- 可重復讀(Repeatable Read):這是MySQL的默認隔離級別。在該級別下,事務在整個事務期間看到的數據保持一致,防止了臟讀和不可重復讀。但仍可能發(fā)生幻讀問題。
- 串行化(Serializable):這是最高的隔離級別,也是最嚴格的。在該級別下,事務被強制排隊執(zhí)行,以避免任何并發(fā)問題,包括臟讀、不可重復讀和幻讀。這提供了最高級別的數據一致性,但也可能降低并發(fā)性能。
5. 事務隔離級別產生并發(fā)事務問題
事務隔離級別,是為了解決多個并行事務競爭導致的數據安全問題的一種規(guī)范。具體來說,多個事務競爭可能會產生三種不同的現象。
- 臟讀。
- 不可重復讀
- 幻讀接下來我們分別介紹一下:
5.1. 臟讀(Dirty Read)
臟讀:一個事務讀到另外一個事務還沒有提交的數據
圖片
示例sql:
1.事務A 中SQL
-- 步驟一:設置事務A隔離級別 讀未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
use test;
-- 步驟三:開啟事務A,查詢ID=1的數據
start transaction;
select * from tb_account where id=1;
-- 步驟五:開啟事務A,查詢ID=1的數據
select * from tb_account where id=1;
commit ;
2.事務B中SQL
-- 步驟二:設置事務B隔離級別 讀未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
use test;
-- 步驟四:開啟事務A,
start transaction;
-- 更新ID=1的數據 mnotallow=money-500
update tb_account set mnotallow=money-500 where id=1;
commit ;
示例結果:
圖片
5.2. 不可重復讀(Non-Repeatable Read)
不可重復讀:一個事務先后讀取同一條記錄,但兩次讀取的數據不同,稱之為不可重復讀
圖片
示例sql:
1.事務A 中SQL
-- 讀已提交隔離級別下 不可重復讀
-- 步驟一:設置事務A隔離級別 讀已提交
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
use test;
-- 步驟三:開啟事務A,查詢ID=1的數據
start transaction;
select * from tb_account where id=1;
-- 步驟六:事務A,查詢ID=1的數據
select * from tb_account where id=1;
-- 步驟八:事務A,查詢ID=1的數據
select * from tb_account where id=1;
commit ;
2.事務B中SQL
-- 讀已提交隔離級別下 不可重復讀
-- 步驟二:設置事務B隔離級別 讀已提交
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
use test;
-- 步驟四:開啟事務B,更新ID=1的數據 mnotallow=money-500
start transaction;
update tb_account set mnotallow=money-500 where id=1;
-- 步驟五:事務B查詢id=1數據
select * from tb_account where id=1;
-- 步驟七:事務B 提交事務 并查詢結果
commit ;
select * from tb_account where id=1;
示例結果
圖片
5.3. 幻讀(Phantom Read)
幻讀:一個事務按照條件查詢數據時,沒有對應的數據行,但是在插入數據時,又發(fā)現這行數據 已經存在,好像出現了 "幻影"。
圖片
示例sql:
1.事務A 中SQL
-- 可重復讀隔離級別下 幻讀
-- 步驟一:設置事務A隔離級別 可重復讀
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
use test;
-- 步驟三:開啟事務A,查詢ID>1的數據
start transaction;
select * from tb_account where id>1;
-- 步驟五:事務A,查詢ID>1的數據
select * from tb_account where id>1;
-- 步驟七:事務A,查詢ID>1的數據
select * from tb_account where id>1;
commit ;
2.事務B中SQL
-- 可重復讀隔離級別下 幻讀
-- 步驟二:設置事務B隔離級別 可重復讀
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
use test;
-- 步驟四:開啟事務B,插入一條數據
start transaction;
insert into tb_account values(3,'張三',2000);
-- 步驟六:提交事務B
commit ;
示例結果
圖片
總結
隔離級別 | 臟讀 | 不可重復讀 | 幻讀 |
讀未提交(Read Uncommitted) | ? | ? | ? |
讀已提交(Read Committed) | ? | ? | ? |
可重復讀(Repeatable Read) | ? | ? | ? |
串行化(Serializable) | ? | ? | ? |
查看事務隔離級別
SELECT @@TRANSACTION_ISOLATION;
設置事務隔離級別
SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED |
READ COMMITTED | REPEATABLE READ | SERIALIZABLE }
- SESSION:表示當前客戶端會話窗口有效
- GLOBAL: 表示對所有客戶端會話窗口有效
注意:事務隔離級別越高,數據越安全,但是性能越低。