MySQL 事務和鎖
阿新 • • 發佈:2021-06-18
1. 事務
2. 鎖
1. 事務
什麼是事務?
事務是指一組業務操作,要麼全部成功,要麼全部失敗。
比如銀行轉賬業務,步驟一:從 A 賬戶減少 300 元;步驟二:向 B 賬戶增加 300 元。為了確保總的金額不變,就要維持資料的一致性,那麼步驟一和步驟二兩個操作必須全確認或者全取消。這裡的每個步驟就可以理解為每個 SQL 語句。
事務的特性:ACID
- 原子性(Atomicity):一個事務要麼全部成功(提交),要麼全部失敗(回滾),不能只完成其中的一部分操作。
- 一致性(Consistency):事務的執行不能破壞資料的完整性和一致性,一個事務在執行之前和執行之後,所有資料都必須處於一致的狀態。
- 隔離性(Isolation):在併發環境中,事務是相互隔離的,一個事務的執行不能被其他事務干擾。
- 永續性(Durability):一旦事務提交,那麼它對於系統或者資料的修改是永久性的。
事務語句
- begin(start transaction):顯式開啟一個事務。
- commit:提交事務。
- rollback:回滾事務。
- set autocommit:設定自動提交模式。
autocommit 預設為 on(開啟),即我們每執行一條 SQL 都相當於一個事務並自動提交。
示例場景
場景 1
Session 1(視窗1)
start transaction; -- 顯式開啟一個事務 deletefrom emp where ename='wang'; select * from emp; -- 資料"wang"已刪除
Session 2(視窗2)
select * from emp; -- 資料"wang"未刪除
Session 1(視窗1)
commit; -- 提交事務 select * from emp; -- 資料"wang"已刪除
Session 2(視窗2)
select * from emp; -- 資料"wang"已刪除
場景2
Session 1(視窗 1)
begin; -- 顯式開啟一個事務 insert into emp values('wang', now(), 4000, 2); select * from emp; -- 新增了"wang"資料 insert into emp values('liu', now(), 90000, 3); select * from emp; -- 新增了"liu"資料
Session 2(視窗 2)
select * from emp; -- 未新增兩條資料
Session 1(視窗 1)
rollback; -- 回滾事務 select * from emp; -- 未新增兩條資料
Session 2(視窗 2)
select * from emp; -- 未新增兩條資料
事務的隔離級別
隔離問題:
- 髒讀:一個事務讀到另一個事務沒有提交的資料。
- 不可重複讀:一個事務讀到另一個事務已提交的資料(update)。
- 虛讀(幻讀):一個事務讀到另一個事務已提交的資料(insert)。
隔離級別:
- Read Uncommitted:讀未提交。允許事務督導其他事務未提交的資料變更。
- 存在 3 個問題:髒讀、不可重複讀、虛讀
- Read Committed:讀已提交。允許事務讀到其他事務已提交的資料變更。
- 解決 1 個問題:髒讀
- 存在 2 個問題:不可重複讀、虛讀
- Repeatable Read:可重複讀。在同一個事務中保證讀到的結果的一致性,是 InnoDB 預設的隔離級別。
- 解決 2 個問題:髒讀、不可重複讀
- 存在 1 個問題:虛讀
- Serializable :序列化。完全隔離了事務之間的影響,一個事務正在讀的資料,另一個事務不允許修改。
- 解決 3 個問題:髒讀、不可重複讀、虛讀
2. 鎖
鎖是一種使各種共享資源在被併發訪問變得有序的機制,目的是為了保證資料的一致性。
鎖的型別
根據加鎖範圍大致劃分:
- 表鎖:表鎖會鎖定整張表,其他事務無法操作或者無法變更操作。(Myisam)
- 行鎖:只會鎖定需要的行,併發性會更好,並且行鎖一定是作用在索引上的。(InnoDB)
根據加鎖功能大致劃分:
- 共享鎖(Shared Lock):一個事務併發讀取某一行記錄所需要持有的鎖。
- 排他鎖/獨佔鎖(Exclusive Locks):一個事務併發更新或刪除某一行記錄所需要持有的鎖。
共享鎖/排他鎖都只是行鎖。
死鎖
死鎖發生在當兩個事務均嘗試獲取對方已經持有的排他鎖時。
在 innodb 中,select 不會對資料加鎖,而update/delete 會加行級別的獨佔鎖。
當資料庫的隔離級別為 Repeatable Read 或 Serializable 時,我們來看以下會發生死鎖的併發事務場景。
表結構示例:
mysql> desc user; +-------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+-------+ | id | int | YES | MUL | NULL | | | age | int | YES | MUL | NULL | | | name | varchar(30) | YES | | NULL | | +-------+-------------+------+-----+---------+-------+ 3 rows in set (0.12 sec)
場景 1:表鎖
當 update 資料未作用於索引時,會發生表鎖:
Session 1 | Session 2 |
begin; | begin; |
select * from user; | select * from user; |
update user set name='test1' where name='xiaoming'; | |
(表)鎖等待解除 | update user set name='test1' where name='xiaodan'; |
死鎖,事務被回滾 |
場景 2:行鎖
當 update 資料作用於索引時,會發生行鎖:
Session 1 | Session 2 |
begin; | begin; |
select * from user; | select * from user; |
update user set name='test1' where id=1; | |
(行)鎖等待解除 | update user set name='test1' where id=2; |
死鎖,事務被回滾 |
場景 3:死鎖回滾
當 InnoDB 檢測到死鎖時,會回滾其中一個事務,讓另一個事務得以完成。
Session 1 | Session 2 |
begin; | begin; |
select * from user; | select * from user; |
update user set name='test2' where id=1; | |
(行)鎖等待解除 | update user set name='test2' where id=1; |
死鎖,事務被回滾 |
mysql> update user set name='test2' where id=1; -- Session 2 ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
死鎖的應對方案
Session 1 | Session 2 |
begin; | begin; |
select * from user; | select * from user; |
update user set name='test3' where id=1; | |
commit; | update user set name='test4' where id=1; |
commit; |
在這個併發場景下,兩個事務均能成功提交,而不會有死鎖。
如何減少死鎖發生
- 使用合適的索引。
- 使用更小的事務。
- 經常性的提交事務,避免事務被掛起。