1. 程式人生 > 其它 >MySQL 事務和鎖

MySQL 事務和鎖

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;  -- 顯式開啟一個事務

delete
from 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;  -- 未新增兩條資料

事務的隔離級別

隔離問題:

  1. 髒讀:一個事務讀到另一個事務沒有提交的資料。
  2. 不可重複讀:一個事務讀到另一個事務已提交的資料(update)。
  3. 虛讀(幻讀):一個事務讀到另一個事務已提交的資料(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;

在這個併發場景下,兩個事務均能成功提交,而不會有死鎖。

如何減少死鎖發生

  1. 使用合適的索引。
  2. 使用更小的事務。
  3. 經常性的提交事務,避免事務被掛起。