Mysql 鎖機制分析

整體業務代碼精簡邏輯如下:

@Transaction
public void service(Integer id) {delete(id);insert(id);
}

數據庫實例監控:

當時通過分析上游問題流量限流解決后,后續找時間又重新分析了下問題發生的根本原因,現將其總結如下:本篇文章會先對 Mysql 中的各種鎖進行分析,包括互斥鎖、間隙鎖和插入意向鎖,讓大家對各種鎖的使用場景有一個了解,然后在此基礎上再對本問題進行分析,希望大家未來再碰到相似場景時,能夠快速的定位問題

Mysql 鎖機制

在 Mysql 中為了解決對同一行記錄并發寫的問題,引入了行鎖機制,多個事務不能同時對一行數據進行修改操作,當需要對數據庫中的一行數據進行修改時,會首先判斷該行數據是否加鎖,如果沒加鎖,那么當前事務加鎖成功,可以進行后續的修改操作;但如果該行數據已經被其他事務加鎖,則當前事務只有等待加鎖的事務釋放鎖后才能加鎖成功,繼續執行修改操作

本篇文章中所有實驗用到的建表語句:

create table `test` (`id` int(11) NOT NULL,`num` int(11) NOT NULL,PRIMARY KEY (`id`),KEY `num` (`num`)
) ENGINE = InnoDB;insert intotest
values
(10, 10),
(20, 20),
(30, 30),
(40, 40),
(50, 50);

Shared and Exclusive Locks

shared(S) lock 表示共享鎖,當一個事務持有某行上的 S 鎖后可以對該行的數據進行讀操作,通過語句 select ... from test lock in share mode 可以添加共享鎖,一般使用的較少,不做過多闡述

exclusive(X) lock 表示互斥鎖,當一個事務對某行數據進行 update 或 delete 操作時都要先獲取到該記錄上的 X 鎖,如果已經有其他事務獲取到了該記錄上的 X 鎖,那么當前事務會阻塞等待直到上一事務釋放了對應記錄上的 X 鎖

S 鎖之間不互斥,多個事務可以同時獲取一條記錄上的 S 鎖 X 鎖之間互斥,多個事務不能同時獲取同一條記錄上的 X 鎖 S 鎖和 X 鎖之間互斥,多個事務不能同時獲取同一條記錄上的 S 鎖和 X 鎖

當多個事務同時去 update 索引上同一條記錄時,都需要先獲取到該記錄上的 X 鎖,所謂的鎖也就是會在內存中生成一個數據結構來記錄當前的事務信息、鎖類型和是否等待等信息。下圖中就是 T1 和 T2 同時去更新 id = 30 的這行記錄,并且 T1 成功獲取到了鎖,其在內存中生成的鎖結構信息中字段 is_wating 為 false,可以繼續執行事務的后續邏輯,而 T2 獲取鎖失敗,則生成的鎖結構信息字段 is_wating 為 true,阻塞等待 T1 上的鎖釋放

互斥鎖在 Mysql 日志中的鎖信息為:lock_mode X locks rec but not gap

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 00: len 4; hex 8000000a; asc     ;;1: len 6; hex 00000000274f; asc     'O;;2: len 7; hex b60000019d0110; asc        ;;

Gap Locks

上一小節中介紹了 Exclusive Locks,該鎖可以避免多個事務同時對一行記錄進行更新操作,但不能解決幻讀的問題,所謂的幻讀就是指一個事務在前后兩次查詢同一個范圍時,后一次查詢到了前一次沒有的記錄

session Asession B
T1select num from test where num > 10 and num < 15 for update; (0 rows)
T2insert into test values(12, 12);
T3select num from test where num > 10 and num < 15 for update; (1 rows)

在上面這個場景中,session A 分別在 T1、T3 時刻進行了兩次范圍查詢,session B 在 T2 時刻插入了一條該范圍內的數據,如果 session A 能在 T3 時刻查詢出 session B 插入的數據,就說明發生了幻讀。此時只使用互斥鎖是無法解決幻讀的,因為 num = 12 的記錄在數據庫中還不存在,不能給其加上互斥鎖來防止 T2 時刻 session B 的插入

因此為了解決幻讀問題,只有引入新的鎖機制,也就是間隙鎖(Gap Locks)。間隙鎖和互斥鎖不同,互斥鎖是行鎖,只會鎖定一行特定的記錄,而間隙鎖則是鎖定兩行記錄之間的空隙,防止其他事務在此間隙中插入新的記錄

引入了間隙鎖之后,session A 在 T1 時刻會給 id = 20 記錄生成一個 Gap Locks,之后 session B 在 T2 時刻想要插入記錄時,需要先判斷待插入位置的后一條記錄上是否存在 Gap Locks,很明顯此時 id = 20 的記錄上已經存在了 Gap Locks,那么session B 就需要在 id = 20 的記錄上生成一個插入意向鎖,并進入鎖等待

間隙鎖在 Mysql 中的鎖日志信息如下:lock_mode X locks gap before rec

RECORD LOCKS space id 133 page no 3 n bits 80 index PRIMARY of table `test`.`test` trx id 38849 lock_mode X locks gap before rec
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 00: len 4; hex 8000001e; asc     30 ;;1: len 6; hex 00000000969c; asc       ;;2: len 7; hex a60000011a0128; asc       (;;3: len 4; hex 8000001e; asc     ;;

間隙鎖雖然解決了幻讀問題,但因每次都會鎖住一段間隙,大大降低了數據庫整體的并發度,且因間隙鎖和間隙鎖之間不互斥,不同事務可以同時對同一間隙加上 Gap Locks,這也往往是各種死鎖產生的源頭

Next-Key Locks

Next-Key Locks 是 (Shard/Exclusive Locks + Gap Locks) 的結合,當 session A 給某行記錄 R 添加了互斥型的 Next-Key Locks 后, 相當于擁有了記錄 R 的 X 鎖和記錄 R 的 Gap Locks

在上面 Gap Locks 的例子中事務 1 加的就是 Next-Key Locks,即同時給 id = 20 的記錄加了 X 鎖和 Gap 鎖

在可重復讀隔離級別下,update 和 delete 操作默認都會給記錄添加 Next-Key Locks,Mysql 中 Next-Key Locks 的鎖日志信息為:lock_mode X

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 00: len 8; hex 73757072656d756d; asc supremum;;Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 00: len 4; hex 8000000a; asc     ;;1: len 6; hex 00000000274f; asc     'O;;2: len 7; hex b60000019d0110; asc        ;

Insert Intention Locks

插入意向鎖(Insert Intention Locks) 也是一種間隙鎖,由 INSERT 操作在行數據插入之前獲取

在插入一條記錄前,需要先定位到該記錄在 B+ 樹中的存儲位置,然后判斷待插入位置的下一條記錄上是否添加了 Gap Locks,如果下一條記錄上存在 Gap Locks,那么插入操作就需要阻塞等待,直到擁有 Gap Locks 的那個事務提交,同時執行插入操作等待的事務也會在內存中生成一個鎖結構,表明有事務想在某個間隙中插入新記錄,但目前處于阻塞狀態,生成的鎖結構就是插入意向鎖

實驗模擬如下:

session 1session 2session 3
T1begin;
T2select * from test where id = 25 for update;
T3insert into test values(26, 26);?(blocked)
T4insert into test values(26, 26);?(blocked)

對于語句 select * from test where id = 25 for update 因當前表中不存在該記錄,在可重復讀隔離級別下,為了避免幻讀,會給 (20, 30] 間隙加上 Gap Locks

從鎖日志可以看出 session 1 給記錄 30 添加了間隙鎖(lock_mode X locks gap before rec)

RECORD LOCKS space id 133 page no 3 n bits 80 index PRIMARY of table `test`.`test` trx id 38849 lock_mode X locks gap before rec
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 00: len 4; hex 8000001e; asc     30 ;;1: len 6; hex 00000000969c; asc       ;;2: len 7; hex a60000011a0128; asc       (;;3: len 4; hex 8000001e; asc     ;;

當 session 2 插入記錄 26 時,會在 B+ 樹中先定位到待插入位置,再判斷插入位置的間隙是否存在 Gap Locks,也就是判斷待插入位置的后一記錄 id = 30 是否存在 Gap Locks,如果存在需要在該記錄上生成插入意向鎖等待

RECORD LOCKS space id 133 page no 3 n bits 80 index PRIMARY of table `test`.`test` trx id 38850 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 00: len 4; hex 8000001e; asc    30 ;;1: len 6; hex 00000000969c; asc       ;;2: len 7; hex a60000011a0128; asc       (;;3: len 4; hex 8000001e; asc     ;;

此時 session 2 和 session 3 都在 id = 30 的記錄上添加了插入意向鎖等待 session 1 上的 Gap Locks 釋放,生成的鎖記錄如下:

線上問題分析

在對 Mysql 中的各種鎖結構有了一個清晰的了解之后,回過頭來再看看前面的線上問題

@Transaction
public void service(Integer id) {delete(id);insert(id);
}

對于上面的業務代碼可能存在下面兩種情況:

  • 傳入的參數 id 在原數據庫中不存在
  • 傳入的參數 id 在原數據庫中存在

本次主要會針對 id 記錄在原數據庫中不存在進行分析

session 1session 2session 3
T1delete from test where id = 15;
T2delete from test where id = 15;delete from test where id = 15;
T3insert into test values(15, 15);
T4insert into test values(15, 15);
T5insert into test values(15, 15);

因 id = 15 在數據庫中不存在,在 T1 時刻 session 1 會給其所在間隙的下一條記錄添加上 Gap Locks,又因 Gap Locks 不互斥, 在 T2 時刻 session 2 和session 3 都會同時獲取到 id = 20 的 Gap 鎖

下圖中 tx: T1、T2、T3 分別代表 session 1、session 2 和 session 3

當在 T3 時刻 session 1 插入 id = 15 的記錄時,會判斷其插入位置的后一條記錄是否存在 Gap Locks,如果存在,則需要在該記錄上生成 Insert Intention Locks 并等待持有 Gap Locks 的事務釋放鎖

在 T4 時刻 session 2 執行插入語句,同樣會因插入位置的后一條記錄中存在 Gap Locks 而需要生成 Insert Intention Locks 等待。此時很明顯就形成了死鎖,session 1 生成插入意向鎖等待 session 2 和 session 3 上的 Gap 鎖釋放,而 session 2 同樣生成插入意向鎖等待 session 1 和 session 3 上的 Gap 鎖釋放

在 T4 時刻檢測到死鎖后,Mysql 會選擇其中一個事務進行回滾,假設此時 session 2 被回滾,釋放了其持有的所有鎖資源,session 1 可以繼續執行嗎? 很明顯不可以,session 1 還同時在等待 session 3 上的 Gap 鎖釋放,繼續阻塞等待

在 T5 時刻 session 3 開始執行插入語句,此時同 T4 時刻,死鎖形成,session 1 生成的插入意向鎖正在等待 session 3 上的 Gap Locks 釋放,session 3 上生成的插入意向鎖正在等待 session 1 上的 Gap Locks 釋放,此時 session 3 回滾釋放所有鎖資源后,session 1 才可以最終執行成功

在完成了三個并發線程的死鎖分析后,可能有人會想雖然有死鎖,但通過死鎖檢測可以很快的檢測出,程序也可以正常的執行,這有什么問題呢? 其實上面沒有問題主要是因為并發量較小,死鎖檢測可以很快檢測出,如果此時將并發量擴大 100 倍甚至 1000 倍后,還會沒有問題嗎?

看看當時出現線上問題時,接口的調用量情況,

進一步在本地模擬 300 個線程并發執行,因人腦并發分析所有事務的執行情況的話會非常復雜,本次只以事務 1 為一個點來進行分析

從圖中可以看到當 T1 在執行插入語句時,需要等待 T2- T101 上持有的 Gap Locks 釋放,之后 T2 - T6 可能同時執行插入語句,然后進行死鎖檢測,事務回滾,看著似乎只要后續有事務執行了插入語句就會執行死鎖回滾,正常運行,但在死鎖檢測的過程中還會有新事務(T101 - T 200 )獲取到 Gap Locks,造成鎖等待隊列中的事務越來越多,而 Mysql 的整體死鎖檢測時間復雜度為 O(n^2),鎖等待隊列中的事務較多時,每一次有新事務進行鎖等待,死鎖檢測都需要遍歷鎖等待隊列中在其之前等待的事務,判斷是否會因自己的加入形成環,此時檢測會非常消耗 CPU 資源,造成數據庫整體性能下降,死鎖檢測耗時增加,Mysql 活躍連接數大幅增加,并且因鎖等待而連接無法釋放,最終造成應用層連接池被打滿

綜上分析,本次出現問題的最主要原因是在短時間內存在大并發的請求對同一行數據進行先刪除再插入操作(先更新再插入同理),造成了死鎖等待,應用層連接池被打滿,大量上游請求超時重試,進一步導致鎖等待,最終影響了所有依賴該數據庫的業務

因此對于未來在業務代碼中存在相似邏輯的地方,一定要做好防重校驗,避免短時間內存在對同一行數據的先更新再插入的并發操作。同時在可重復讀隔離別下,更新和刪除操作默認都會添加 Next-Key Locks,間隙鎖的引入使得死鎖問題在并發情況下很容易出現,這也是在業務邏輯實現上需要考慮的問題。

總結

本文以一個線上問題為背景,對 Mysql 中的各種鎖機制進行了詳細的總結,分析了各個鎖的加鎖時機和具體使用場景,其中特別要注意間隙鎖的使用,因間隙鎖和間隙鎖之間不互斥,當多個事務之間并發執行時很容易形成死鎖

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/165595.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/165595.shtml
英文地址,請注明出處:http://en.pswp.cn/news/165595.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

XDR 網絡安全:技術和最佳實踐

擴展檢測和響應&#xff08;XDR&#xff09;是一種安全方法&#xff0c;它將多種保護工具集成到一個統一的集成解決方案中。它為組織提供了跨網絡、端點、云工作負載和用戶的廣泛可見性&#xff0c;從而實現更快的威脅檢測和響應。 XDR的目標是提高威脅檢測的速度和準確性&…

Arduino驅動防水型SHT20溫濕傳感器(溫濕度傳感器)

目錄 1、傳感器特性 2、控制器和傳感器連線圖 3、驅動程序 SHT20防水型溫濕傳感器,采用新一代Sensirion濕度和溫度傳感器,配有4代CMOSens芯片。除了配有電容式相對濕度傳感器和能隙溫度傳感器外,該芯片還包含一個放大器、A/D轉換器、OTP內存和數字處理單元,可精確測量周…

Linux系統介紹及文件類型和權限

終端:CtrlAltT 或者桌面/文件夾右鍵,打開終端 切換為管理員:sudo su 退出:exit 查看內核版本號:uname -a 內核版本號含義:5 代表主版本號;13代表次版本號;0代表修訂版本號;30代表修訂版本的第幾次微調;數字越大表示內核越新. 目錄結構 /bin:存放常用命令(即二進制可執行程序…

C/C++內存管理(2):`new`和`delete`的實現原理

new和delete操作自定義類型 class Stack { public:Stack(int capacity 3):_top(0), _capacity(capacity){cout << "Stack(int capacity 3)" << endl;_a new int[capacity];}~Stack(){cout << "~Stack()" << endl;delete _a;_to…

openssl+ RSA + linux 簽名開發實例(C++)

文章目錄 一、opensslRSA理論基礎二、openssl RSA 簽名開發實例 一、opensslRSA理論基礎 RSA簽名是一種非對稱加密算法&#xff0c;用于在信息傳輸過程中驗證消息的完整性和真實性。以下是RSA簽名的理論基礎的主要知識點&#xff1a; RSA密鑰對&#xff1a; RSA使用一對公鑰和…

pcie-2-rj45速度優化

背景: 目前用iperf3打流傳輸速率達不到要求,千兆實際要求跑到800M以上: 優化方案: 1.優化defconfig: 首先編譯user版本驗證看是否正常 debug版本關閉CONFIG_SLUB_DEBUG_ON宏控。 2.找FAE ,通過更換驅動,或者更新驅動來優化 3.綁定大核: 以8125網卡為例,udp…

【Unity】IBeginDragHandler、IDragHandler 和 IEndDragHandler 介紹

IBeginDragHandler、IDragHandler 和 IEndDragHandler 介紹 IBeginDragHandler、IDragHandler 和 IEndDragHandler 是 Unity 引擎中的三個接口&#xff0c;用于處理 UI 元素的拖放事件。這些接口通常結合使用&#xff0c;構成了 Unity 引擎的拖放事件系統。 IBeginDragHandler…

java--權限修飾符

1.什么是權限修飾符 就是是用來限制類中的成員(成員變量、成員方法、構造器、代碼塊...)能夠被訪問的范圍。 2.權限修飾符有幾種&#xff1f;各自的作用是什么&#xff1f; private<缺省<protected<public(范圍由小到大)

什么年代了,還不會 CI/CD 么?

目錄 什么是 CI/CD&#xff1f; CI/CD 對業務有哪些好處&#xff1f; 一&#xff1a;確保卓越的代碼質量 二&#xff1a;更快的發布速度 → 更快的交付 三&#xff1a;自動化降低成本 四&#xff1a;故障隔離 五&#xff1a;簡化回滾 六&#xff1a;持續反饋 七&#…

設計模式——行為型模式(二)

6.8 迭代器模式 6.8.1 概述 定義:提供一個對象來順序訪問聚合對象中的一系列數據,而不暴露聚合對象的內部表示。 6.8.2 結構 迭代器模式主要包含以下角色: 抽象聚合(Aggregate)角色:定義存儲、添加、刪除聚合元素以及創建迭代器對象的接口。具體聚合(ConcreteAggreg…

C# Onnx PP-Vehicle 車輛分析(包含:車輛檢測,識別車型和車輛顏色)

目錄 效果 模型信息 mot_ppyoloe_s_36e_ppvehicle.onnx vehicle_attribute_model.onnx 項目 代碼 下載 其他 C# Onnx PP-Vehicle 車輛分析&#xff08;包含&#xff1a;車輛檢測&#xff0c;識別車型和車輛顏色&#xff09; 效果 模型信息 mot_ppyoloe_s_36e_ppvehi…

009 OpenCV 二值化 threshold

一、環境 本文使用環境為&#xff1a; Windows10Python 3.9.17opencv-python 4.8.0.74 二、二值化算法 2.1、概述 在機器視覺應用中&#xff0c;OpenCV的二值化函數threshold具有不可忽視的作用。主要的功能是將一幅灰度圖進行二值化處理&#xff0c;以此大幅降低圖像的數…

ASP.NET Core 啟用CORS

瀏覽器的安全阻止一個域的本地頁面請求另外不同域的本地頁面&#xff0c;這個限制叫同源策略&#xff0c;這個安全特性用來阻止惡意站點從別的網站讀取數據 例如假如我有一個頁面叫A.html https://foo.example/A.html 現在頁面A.html有一個ajax代碼嘗試讀取B.html的HTML的源…

【PyQt】(自定義類)陰影遮罩

寫了一個感覺有些用的小玩具。 用于給控件添加陰影遮罩(強調主控件的同時屏蔽其余控件的點擊) 自定義陰影遮罩Mask&#xff1a; from PyQt5.QtCore import QPoint,QRect,Qt,QPoint,QSize from PyQt5.QtWidgets import QWidget,QLabel,QPushButton,QVBoxLayout from PyQt5.QtGu…

leetcode:合并兩個有序鏈表

題目描述 題目鏈接&#xff1a;21. 合并兩個有序鏈表 - 力扣&#xff08;LeetCode&#xff09; 題目分析 這個算法思路很簡單&#xff1a;就是直接找小尾插 定義一個tail和head&#xff0c;對比兩個鏈表結點的val&#xff0c;小的尾插到tail->next&#xff0c;如果一個鏈表…

每日一題:LeetCode-589.N叉樹的前序遍歷序列構造二叉樹

每日一題系列&#xff08;day 01&#xff09; 前言&#xff1a; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f308; &#x1f50e…

企業微信身份驗證

本篇主要是在上一篇獲取第三方憑證基礎上&#xff0c;用戶通過三方網站自定義授權登錄后獲取用戶信息&#xff0c;以實現用戶綁定登錄功能。 構造第三方應用授權鏈接 如果第三方應用需要在打開的網頁里面攜帶用戶的身份信息&#xff0c; 第一步需要構造如下的鏈接來獲取授權c…

馬養殖場建設VR模擬實訓教學平臺具有靈活性和復用性

為保障養殖場生物安全&#xff0c;避免疫病傳播&#xff0c;學生出入養殖場受時間和地域的限制&#xff0c; 生產實習多以參觀為主&#xff0c;通過畜牧企業技術人員的講解&#xff0c;學生被動了解生產過程。為了解決畜牧養殖實訓難的問題&#xff0c;借助VR技術開展畜牧養殖虛…

通過云服務器部署JavaWeb項目

文章目錄 搭建Java運行環境部署項目更改部分項目代碼打包項目把war包上傳到webapps目錄下驗證程序 搭建Java運行環境 搭建環境的部分比較復雜&#xff0c;為了讓大家的思路更加清晰特別總結為一篇博客點擊查看 部署項目 更改部分項目代碼 打包項目 把war包上傳到webapps目錄…

大洋鉆探系列之三IODP 342航次是干什么的?(下)

上文簡要地介紹IODP342航次的總體情況&#xff0c;本文以航次1個鉆孔&#xff08;U1403&#xff09;為例&#xff0c;更為詳細地系統展示大洋鉆探航次的工作和成果。 ?編輯? 站位疊加多波束影像的成果圖見下圖&#xff0c;從圖中的顏色效果可以看出&#xff0c;此多波束的成…