MySQL 事務與鎖機制深入

MySQL事務與鎖機制


什麼是數據庫的事務

數據庫事務( transaction)是訪問並可能操作各種數據項的一個數據庫操作序列,這些操作要麼全部執行,要麼全部不執行,是一個不可分割的工作單位。事務由事務開始與事務結束之間執行的全部數據庫操作組成。

事務性質ACID

  1. 原子性(Atomicity)

    事務中的全部操作在數據庫中是不可分割的,要麼全部完成,要麼全部不執行。

  2. 一致性(Consistency)

    幾個並行執行的事務,其執行結果必須與按某一順序 串行執行的結果相一致。

    事務前後數據的完整性必須保持一致。事務操作成功後,數據庫所處的狀態和他的業務規則是一致的,即數據不會被破壞。如A賬戶轉賬100元到B賬戶,不管操作成功與否,A和B賬戶的存款總額是不變的。

  3. 隔離性(Isolation)

    事務的執行不受其他事務的干擾,事務執行的中間結果對其他事務必須是透明的。

  4. 持久性(Durability)

    對於任意已提交事務,系統必須保證該事務對數據庫的改變不被丟失,即使數據庫出現故障。

開啓和關閉事務

show GLOBAL VARIABLES LIKE ‘autocommit’

可以查詢當前數據庫是否開啓自動提交功能

-- BEGIN 用於開啓事務,COMMIT,ROLLBACK用來結束事務,客戶端中斷也會結束事務
BEGIN; -- 開啓事務
   UPDATE USER t set t.age=12 WHERE t.id=1;
COMMIT; -- 提交事務
ROLLBACK; -- 回滾事務

併發下引起有事務問題

select @@tx_isolation; – 查看當前數據的隔離級別

保證數據庫隔離級別爲 READ-UNCOMMITTED否則事務將無法測試。

  • 髒讀:一個事務讀取到另一個事務未提交的數據
    在這裏插入圖片描述

    從圖中可以看到事務A在事務B提交修改數據之前讀到修改的數據。

  • 幻讀:在同一事務中多次進行,由於其他提交事務所做的新增,每次返回不同的結果集。

    在這裏插入圖片描述

    從圖中可以看到事務A讀取到事務B新增的數據。

  • 不可重複讀:在同一事務中多次進行,由於其他提交事務所做的修改和刪除,每次返回不同的結果集。

在這裏插入圖片描述

從圖中可以看到事務A讀取到事務B修改的數據。

事務隔離級別(SQL92)

在這裏插入圖片描述

如何解決讀一致性問題?

  1. 在讀取數據前,對其加鎖,阻止其他事務對數據進行修改——Lock Based Concurrency Control (LBCC)
  2. 生成一個數據請求時間點的一致性數據快照(Snapshout),並用這個快照來提供一定級別(語句級或事務級)的一致性讀取——Multi-Version Concurrency Control (MVCC)

鎖的粒度及區別

表鎖與行鎖的區別

​ 鎖定粒度:表鎖 > 行鎖

​ 加鎖效率:表鎖 > 行鎖

​ 衝突概率:表鎖 > 行鎖

​ 併發性能:表鎖 > 行鎖

MyISAM 鎖的粒度爲表鎖,InnoDB 鎖的數度爲行鎖

鎖的模式

-- 查詢鎖的使用情況
SELECT * FROM information_schema.INNODB_LOCKS t;
SELECT * from information_schema.INNODB_LOCK_WAITS t;
  • 共享鎖(行鎖):Shared Locks
  • 排它鎖(行鎖):Exclusive Locks
  • 意向共享鎖(表鎖):Intention Shared Locks
  • 意向排它鎖(表鎖):Intention Exclusive Locks
  • 插入意向鎖(Insert Intention Locks)
  • 自增鎖(AUTO-INC Locks)

鎖的算法

  • 記錄鎖:(Record Locks)
  • 間隙鎖:(Gap Locks)
  • 臨鍵鎖:(Next-key Locks)

共享鎖(行鎖)

共享鎖(S鎖)又稱爲讀鎖,顧名思義,共享鎖就是多個事務對於同一數據可以共享一把鎖,都能訪問到數據,但是只能讀不能修改

加鎖方式:select … Lock in share mode

釋放鎖:commit/rollback

-- TRANSACTION A
BEGIN;
SELECT * from `user`  LOCK in SHARE MODE;
COMMIT;
-- TRANSACTION B
BEGIN;
SELECT * from `user`  LOCK in SHARE MODE;
COMMIT;

事務A和事務B都可以查詢到數據因此共享鎖可以讀。

-- TRANSACTION A
BEGIN;
SELECT * from `user`  LOCK in SHARE MODE;
-- COMMIT;
-- TRANSACTION B
BEGIN;
  DELETE FROM `user`;
ROLLBACK;

可以看到事務B再執行的時候處於阻塞狀態只有事務A提交後,事務B纔會繼續執行。因此共享鎖只能讀不能修改

排他鎖(行鎖)

排它鎖(X鎖)又稱爲寫鎖,排他鎖不能與其他鎖並存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的鎖(共享鎖,排他鎖),只有獲取了排他鎖的事務可以對數據行進行讀取和修改。

加鎖方式:

​ 自動:delete/update/insert 默認加上X鎖

​ 手動:select … FOR UPDATE

-- TRANSACTION A
BEGIN;
-- 自動加寫鎖
UPDATE `user` t set t.age=45 WHERE t.id=1
-- COMMIT;
-- TRANSACTION B
BEGIN;
  -- 共享鎖
  SELECT * from `user` LOCK IN SHARE MODE;
  -- 手動加排他鎖
  SELECT * FROM `user` FOR UPDATE;
  -- 自動加排他鎖
  DELETE FROM `user`  WHERE id=1; 
ROLLBACK;

可以看到事務B再執行的時候無論是共享鎖還是排他鎖都是阻塞狀態,因此排他鎖不能與其他鎖並存。

意向共享鎖/意向排它鎖(表鎖)

意向鎖是由數據引擎自己維護的,用戶無法手動操作意思鎖。

  • 意向共離鎖(Intention Shared Locks,簡稱IS鎖)表示事務準備給數據行加入共享鎖,也就是說一個數據行加共享鎖前必須先取得該有的IS鎖。
  • 意向排他鎖(Intention Exclusive Locks,簡稱IX鎖)表示事務準備給數據加入排他鎖,說明事務在一個數據行加排他鎖前必須先取處該表的IX鎖。

爲什麼意向鎖是表級別的?

當我們需要加一個排他鎖時,需要根據意向鎖去判斷表中有沒有數據行被鎖定(行鎖);

  1. 如果意向鎖是行鎖,則需要遍歷每一行數據去確認
  2. 如果意向鎖是表鎖,則只需要判斷一次即可知道有沒數據行被鎖定,提升性能。

共享鎖/排他鎖與意向共享鎖/意向排他鎖的兼容性關係

X IX S IS
X 互斥 互斥 互斥 互斥
IX 互斥 兼容 互斥 兼容
s 互斥 互斥 兼容 兼容
IS 互斥 兼容 兼容 兼容

這裏需要重點關注的是IX鎖和IX鎖是相互兼容的,這可能會造成死鎖。

插入意向鎖(行鎖)

插入意向鎖是一種特殊的間隙鎖,但不同於間隙鎖的是,該鎖只用於併發插入操作。如果說間隙鎖鎖住的是一個區間,那麼插入意向鎖鎖住的就是一個點。因而從這個角度來說,插入意向鎖確實是一種特殊的間隙鎖。與間隙鎖的另一個非常重要的差別是:儘管插入意向鎖也屬於間隙鎖,但兩個事務卻不能在同一時間內一個擁有間隙鎖,另一個擁有該間隙區間內的插入意向鎖(當然,插入意向鎖如果不在間隙鎖區間內則是可以的)。這裏我們再回顧一下共享鎖和排他鎖:共享鎖用於讀取操作,而排他鎖是用於更新或刪除操作。也就是說插入意向鎖、共享鎖和排他鎖涵蓋了常用的增刪改查四個動作。

自增鎖(表鎖)

AUTO-INC鎖是一種特殊的表級鎖,發生涉及AUTO_INCREMENT列的事務性插入操作時產生。

鎖的算法詳解

表如圖:

在這裏插入圖片描述

現有user表ID爲主鍵,其中四條記錄ID分別爲1,4,7,10

記錄鎖

唯一性索引(唯一/主鍵)等值查詢,精確匹配。數據存在

在這裏插入圖片描述

-- 鎖住:ID=4
BEGIN;
  SELECT * from `user` t WHERE t.id=4  for update;
-- COMMIT;
-- 查看鎖有類型爲排他鎖(X),
SELECT * FROM information_schema.INNODB_LOCKS t;

在這裏插入圖片描述

間隙鎖

記錄不存在的情況,間隙鎖解決幻讀。
在這裏插入圖片描述

-- TRANSACTION A 鎖定(4,7)
BEGIN;
  SELECT * from `user` t WHERE t.id>4 AND t.id<7  for update;
-- COMMIT;
-- TRANSACTION B 鎖定(4,7)
BEGIN;
  SELECT * from `user` t WHERE t.id=6  for update;
-- COMMIT;

兩個事務鎖定的空間都 是(4,7)所以GAP鎖之間不衝突。

-- TRANSACTION A 鎖定(4,7)
BEGIN;
  SELECT * from `user` t WHERE t.id>4 AND t.id<7  for update;
-- COMMIT
-- TRANSACTION B 插入id爲6的記錄
BEGIN;
  INSERT INTO `user` VALUES(6,'six',22);
COMMIT ;

因爲事務A鎖定了(4,7)所以事務B處理等待狀態,只有事務A提交後事務B纔可以後續操作。

-- 查看鎖有類型爲排他鎖(X),
SELECT * FROM information_schema.INNODB_LOCKS t;

在這裏插入圖片描述

臨鍵鎖

範圍查詢,包含記錄和區間。

在這裏插入圖片描述

-- TRANSACTION A 鎖定(4,7] (7,10] 左開右閉
BEGIN;
  SELECT * from `user` t WHERE t.id>5 AND t.id<9  for update;
-- COMMIT;
-- TRANSACTION B
BEGIN;
  INSERT INTO `user` VALUES(6,'six',22);
COMMIT ;

因爲事務A鎖定了(4,7] (7,10]所以事務B處理等待狀態,只有事務A提交後事務B纔可以後續操作。

-- 查看鎖有類型爲排他鎖(X),
SELECT * FROM information_schema.INNODB_LOCKS t;

在這裏插入圖片描述

死鎖示例

CREATE TABLE `test` (
  `id` int(20) NOT NULL,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
session1 session2
begin;
begin;
select * from test where id = 12 for update;
先請求IX鎖併成功獲取
再請求X鎖,但因行記錄不存在,故得到的是間隙鎖(10,15)
select * from test where id = 13 for update;
先請求IX鎖併成功獲取
再請求X鎖,但因行記錄不存在,故得到的是間隙鎖(10,15)
insert into test(id, name) values(12, “test1”);

請求插入意向鎖(12),因事務二已有間隙鎖,請求只能等待
鎖等待中 insert into test(id, name) values(13, “test2”);

請求插入意向鎖(13),因事務一已有間隙鎖,請求只能等待
鎖等待解除 死鎖,session 2的事務被回滾

在場景中,因爲IX鎖是表鎖且IX鎖之間是兼容的,因而事務一和事務二都能同時獲取到IX鎖和間隙鎖。另外,需要說明的是,因爲我們的隔離級別是RR,且在請求X鎖的時候,查詢的對應記錄都不存在,因而返回的都是間隙鎖。接着事務一請求插入意向鎖,這時發現事務二已經獲取了一個區間間隙鎖,而且事務一請求的插入點在事務二的間隙鎖區間內,因而只能等待事務二釋放間隙鎖。這個時候事務二也請求插入意向鎖,該插入點同樣位於事務一已經獲取的間隙鎖的區間內,因而也不能獲取成功,不過這個時候,MySQL已經檢查到了死鎖,於是事務二被回滾,事務一提交成功。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章