成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

探秘 MySQL 鎖:原理與實踐

數據庫 MySQL
當我們深入探索 MySQL 的領域時,不得不將目光聚焦于這看似神秘卻又至關重要的鎖。讓我們一同開啟這場關于 MySQL 鎖的探索之旅,逐步揭開它的神秘面紗,去洞悉它背后的深刻原理和廣泛應用。

在當今數字化的時代,數據庫管理系統的重要性不言而喻,而 MySQL 作為廣泛應用的數據庫之一,更是有著舉足輕重的地位。在 MySQL 的復雜世界中,鎖機制宛如一把關鍵的鑰匙,它既是保障數據一致性和完整性的堅實衛士,也是影響數據庫性能和并發處理能力的重要因素。

當我們深入探索 MySQL 的領域時,不得不將目光聚焦于這看似神秘卻又至關重要的鎖。它究竟是如何運作的?有哪些不同的類型和特點?又如何在各種業務場景中發揮著關鍵作用?讓我們一同開啟這場關于 MySQL 鎖的探索之旅,逐步揭開它的神秘面紗,去洞悉它背后的深刻原理和廣泛應用,為我們更好地駕馭 MySQL 數據庫奠定堅實基礎。

注意:本文所有知識點都是從MySQL8.0版本進行討論,針對5.7版本筆者會在特定知識點中點出區別。

一、詳解MySQL鎖原理

1. 詳解共享鎖和排他鎖

共享鎖(Share Lock,S 鎖):又稱讀鎖,進行讀取操作時會上的鎖,上讀鎖之后的鎖其他事務上讀鎖也沒問題。

排他鎖(Exclusive Lock,X 鎖):又稱寫鎖,進行修改操作前就需要上一把X鎖,上了寫鎖的數據,其他事務則無法上讀鎖或者寫鎖。對于已經上了讀鎖的數據,寫鎖自然也是不能上鎖的。

有了上述基礎,我們針對x鎖對應delete和update操作進行一定的拓展,當事務進行delete的操作時,對應的步驟為:

  • 定位要刪除的數據的B+樹。
  • 對其上X鎖。
  • 上鎖成功后將該位置標志位delete mark。
  • 提交事務。

可以看到刪除操作本質就是定位、標記、刪除對應3個步驟,而且刪除也并非實際意義上的刪除,而是標記刪除。

針對update操作,我們可以從以下3個維度探討:

(1) 普通更新,即更新的字段物理空間不會變化,例如update tb set age=19 where id=1,該操作操作和上述操作差不多,定位到B+樹上的數據后上X鎖,并進行修改操作:

(2) 更新可變字段,假設我們基于主鍵更新某個varchar字段,例如update tb set name='aaaaaaaaaaaaaa' where id=1,將name由varchar(1)更新為varchar(14),那么對應的操作就是定位到數據后上X鎖,將數據刪除(這里的刪除就是將記錄移動到垃圾鏈表),然后執行insert操作。

(3) 更新主鍵,該操作因為會修改b+樹,在定位到數據上X鎖之后,先按照delete的方式將數據刪除,然后再執行insert操作即可。

需要了解的是MySQL在進行讀寫操作為保證并發性能并非一定會用到讀寫鎖,在可重復讀和讀已提交兩個級別下,由于mvcc機制的存在,所以MySQL事務的讀操作都是基于readview進行數據查詢的。

深入解讀MySQL的MVCC與事務隔離級別

2. 詳解意向鎖

假如我們要上全表鎖,我們就必須知道這張表有沒有上過讀鎖或者寫鎖的行級鎖,要想做到這點,常規做法是一行一行遍歷過去看看,針對大表而言,這種遍歷的效率是非常低的。

意向鎖就是用于解決這個問題的,它是表級鎖,在事務需要上讀鎖(S鎖)或者寫鎖(X鎖)前,首先必須取得意向鎖,這樣某些事務需要上全表鎖時,只需要看看有沒有事務持有意向鎖即可:

需要補充的是,意向鎖由數據引擎自行維護,用戶是無法操作的。

說完意向鎖的作用,我們就可以再來聊聊意向鎖的種類,意向鎖分為意向共享鎖和意向排他鎖:

  • 意向共享鎖(Intention Shared Lock,IS 鎖):當事務需要上S鎖時,就需要先嘗試獲取IS鎖。
  • 意向排他鎖(Intention Exclusive Lock,IX 鎖):當事務需要針對某條數據上X鎖的時候,就需要先上一把IX鎖。 而IS和IX之間互相兼容,彼此不互斥,例如:一個事務上了IS,其他事務同樣可以上IS和IX,因為IS本質就是一個共享讀鎖,某個事務持有這個IS鎖之后上的可能是id為1的讀鎖。不影響其他事務上IS或IX后對id為2的數據上X鎖:

本質上來說IS和IX鎖都是表級鎖,它們主要是解決在事務上S鎖或者X鎖前意向詢問來避免掃表的開銷,所以彼此之間是兼容的。

接下來我們從表記鎖的角度探討以下意向鎖和讀寫鎖之間的關系,假設我們的事務需要上IS鎖之后針對表上了S表級鎖,那么就需要查看是否有事務上了IX鎖,如果有則說明有事務正在進行寫操作,此時我們的S表級鎖操作就會阻塞:

同理假設我們的嘗試IX鎖進行修改操作,需要針對全表上了X鎖,此時我們就需要檢查是否有其他事務上了IS或者IX以確定是否存在數據操作,如果有則阻塞等待,這就是IS、IX鎖對于數據讀寫操作的巧妙設計:

3. 詳解表記鎖

表級鎖(table-level locking)即可每一次操作時,鎖的是整張表,鎖的粒度大,上鎖速度快,開銷低,一旦某個事務上了表級鎖,那么其他事務就無法再上行級鎖或者表鎖,這也就意味著高并發場景下性能非常差:

如下所示,這條SQL語句查詢上的就是表級鎖:

SELECT * FROM s1   for update;

這一點我們可以通過查看performance_schema.data_locks表印證這一點,可以看到這個事務上的是IX意向寫鎖,并將表中的所有的記錄即都上了record寫鎖:

4.詳解行級鎖

行級鎖(table-level locking)鎖的粒度相對小一些,是針對索引字段加鎖,鎖的是選定的行數據,相比前者上鎖速度會慢一些,因為需要定位到當前行才能鎖定,并且因為粒度和邏輯設計問題有可能會導致死鎖,但是對于高并發多事務會相對友好一些:

對應我們給出行級鎖的使用示例:

SELECT * FROM s1 WHERE id=1 for update;

通過performance_schema.data_locks表,可以看到這條該事務上X鎖前上了一把IX鎖之后,針對這條記錄上了X,REC_NOT_GAP即上了一把行級鎖,但是不鎖住間隙:

注意:我們上文所討論的都是針對innodb這個存儲引擎,如果是MyISAM這個存儲引擎僅僅支持表級鎖,而InnoDB支持行級鎖。

5.行級鎖實際使用示例

我們先給出行鎖的使用示例:首先創建一張測試表,注意id被設置為主鍵是自帶索引的。

drop  table user;

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB CHARSET=utf8;

插入模擬數據:

insert into `user`(username, age)values
 ('tom',23),
('joey',22),
('James',21),
('William',20),
('David',24);

關閉事務自動提交,并確認:

-- 關閉事務自動提交
set  autocommit = 0;

-- 查看自動提交是否生效
show VARIABLES like 'autocommit';

此時,在窗口鍵入一下SQL進行查詢,可以看到筆者用了for update對id為1的數據上了排他鎖。

begin;
select * from `user` u where id=1 for update ;

此時我們再開一個排他鎖查詢,可以看到事務阻塞:

begin;
select * from `user` u where id=1 for update ;

由此可知,使用排他鎖對索引列上鎖時,其他排他鎖是無法操作對應數據行的:

補充一個聯合索引的行級鎖使用示例,基于上述表格我們創建一個表級鎖:

create index index_username_age on user(username,age);

開啟一個事務進行查詢:

begin;
select * from user where username ='tom' and age=23  for update ;

此時另一個事務進行相同查詢會阻塞,走不相同的查詢條件不會阻塞,說明聯合索引也是走行級鎖:

begin;
select * from user where username ='tom' and age=23  for update ;

我們通過performance_schema.data_locks表可以看到,第一個事務針對tom數據上了一把行級寫鎖:

6. 表級鎖示例

上文提到,只要不走索引的查詢鎖定的都是整張表,所以我們使用age對應22的SQL查詢語句上排他鎖:

begin;
select * from user where age=20 for update;

再打開另一個窗口就會發現,窗口被阻塞:

begin;
select * from user where age=22 for update ;

很明顯因為沒有命中索引所以無法通過索引方式進行定位,所以上了表級鎖,這一點我們也可以通過information_schema.INNODB_TRX表查看,可以看到第一個事務將所有數據行都上了鎖:

7. 行級鎖使用的注意事項

我們都知道行級鎖鎖的是索引字段,而表級鎖鎖的是非索引字段,這就意味著如果我們進行update或者delete操作 (這兩個操作會上寫鎖互斥的,后文會說明)時where條件沒有命中唯一索引或者索引失效的話,就會上表級鎖,進而出現性能問題。

當然了,有時候MySQL優化器也會不走索引,例如范圍索引檢索范圍區間超過30%,優化器就會走全表掃描,那就無能為力了。

8. InnoDB有哪幾類鎖

  • 記錄鎖(Record Lock):這個鎖鎖定的是單個記錄行上的鎖。
  • 間隙鎖(Gap Lock):這個鎖鎖定的是一個范圍,例如查找id>21的用戶,那么間隙鎖鎖定的就是大于21的記錄,不包括21本身。
  • 臨建鎖(Next-key Lock):這個鎖我們可以理解為是記錄鎖和間隙鎖的綜合,它可以保證當前鎖定的記錄及其間隙都上鎖,這個鎖可以保證可重復讀場景下事務讀避免幻讀問題,例如我們數據正在讀取id為8的數據,針對該鎖上一把Next-key Lock可以阻塞當前數據及其前后區間被其他事務操作導致幻讀:

9. 可重復讀是基于上面那種鎖避免幻讀的呢?

可重讀讀避免幻讀的方式有兩種:

  • 快照讀(一次性非鎖定讀):這種方式就是通過mvcc的方式僅僅在啟動時創建一個readView確保不會出現幻讀。
  • 當前讀(一次性鎖定讀):這種方式就是通過臨建鎖,避免新的記錄插入從而避免幻讀的情況發生。

具體可以參考筆者的這篇文章:《深入解讀MySQL的MVCC與事務隔離級別

10. 詳解間隙鎖

上文說過,間隙鎖就是在范圍查詢時對索引項上鎖,但不包括范圍本身的一種鎖,這種機制在RR這個隔離級別可以一定程度上避免幻讀,注意是一定程度上。 對此我們不妨舉個例子:

首先我們創建一個用戶表,并對age加個索引:

drop  table user;

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB CHARSET=utf8;


create index age_index on user(age);

然后插入數據:

insert into `user`(username, age)values ('tom',23),('joey',22),('James',21),('William',20),('David',24);

由于MySQL默認隔離級別為RR,所以我們開啟一個窗口設置關閉自動提交,并查看其是否生效。

-- 關閉事務自動提交
set  autocommit = 0;

-- 查看自動提交是否生效
show VARIABLES like 'autocommit';

若生效,我們則輸入一個begin,并進行一個范圍查詢,搜索大于24范圍的數據。

begin;
select * from user where age >24 for update;

當我們在開啟一個新的窗口并進行插入操作時可以發現,操作被阻塞了。

begin;
insert into `user`(username, age)values ('tom',26);

因為年齡上了索引,所以查詢時走了范圍索引,從performance_schema.data_locks可以看出該操作針對id為5、10這兩條年齡為24的數據都上了行級鎖和間隙鎖,導致數據插入失敗:

11.詳解悲觀鎖和樂觀鎖

悲觀鎖就我們上面所說的互斥鎖,它認為自己每次拿到的數據都很可能被人修改,通過上鎖確保互斥性避免數據一致性問題。在MySQL中select...for update,insert,update、delete語句用的都是用排他鎖實現悲觀鎖。 而樂觀鎖則是一種業務鎖,通過用戶手動對表增加一個版本號的字段來解決并發數據正確性問題,如下圖所示,某個時間點兩個相同的事務讀取到相同版本號的當行數據,彼此都執行更新邏輯,為了保證并發更新保證數據準確性,我們就需要通過版本號確保自己更新的數據是最新數據,如下圖所示,左邊的更新SQL最先執行,這就使得右邊的更新失敗了,這樣右邊的事務就知道自己更新條件數據已過期,就會修改版本號再次進行更新。

12. 當前讀和快照讀有什么區別?

答:  先說說當前讀(一致性非鎖定讀)吧,當前讀發生在讀已提交(RC)或者可重復讀(RR)這兩個隔離級別下,我們使用的select使用的就是快照讀:

若在RC這個隔離級別下,用戶每次進行讀操作時,都會創建一個readView,然后通過這個readView獲取數據。

若在RR這個隔離級別下,僅僅在啟動事務時創建一個readVew,后續無論其他事務無論修改用戶讀取的數據,用戶都只會讀取當前readView的數據,這就是為什么RR可以保證可重入讀。

而當前讀(一致性鎖定讀)則基于S鎖或者X鎖實現的一種讀取最新數據,快照讀的select語句如下:

SELECT ... FOR UPDATE
SELECT ... LOCK IN SHARE MODE

當然,我們的insert、update、delete語句也是使用當前讀。

二、詳解MySQL中死鎖問題

1. 詳解定位事務各種鎖的幾張表

在正式演示死鎖定位與排查思路之前,我們先簡單介紹幾張比較重要的表,首先是INNODB_TRX 這張表,它會記錄當前活躍事務所持有的鎖的情況:

SELECT * FROM information_schema.INNODB_TRX it ;

如下圖,可以看到我們567584這個事務,這里我們著重查看trx_tables_locked、trx_lock_structs、trx_rows_locked三個字段,其含義分別是:

  • trx_tables_locked:當前事務對幾張表上鎖,以本條數據為例就上了一把鎖。
  • trx_rows_locked:標識當前事務鎖定幾行數據,下圖表示當前事務鎖定了一行數據。
  • trx_lock_structs:當前事務生成幾個鎖的結構體,顯示為2,即生成兩個鎖的結構體。

我們再來看看data_locks表(對應MySQL5.7版本表明為innodb_locks),這張表在MySQL中活躍事務的上鎖情況:

select * from performance_schema.data_locks;

如下圖,可以看到567834事務的線程號、事件id以及這個事務在tb_1表上了一把IX意向讀鎖:

最后再來看看 data_lock_waits表(對應MySQL5.7是innodb_waits表),這張表就比較重要了,它代表了當前事務中處于等待鎖狀態的事務情況:

select * from performance_schema.data_lock_waits;

如下圖,可以看到本文的567585事務正在等待567584的事務的鎖:

2. (實踐)線上定位MySQL死鎖與解決思路

接下來我們就基于一個簡單的例子來掩飾一下如何定位死鎖問題,我們都知道for update上的是寫鎖,這意味著一旦上了X鎖的數據,其他事務就無法針對該數據上S鎖或者X鎖:

如下圖這個說明,假設的我們的事務1先針對id為1的值上了一把寫鎖,對應事務的SQL如下:

begin;
SELECT * FROM  tb_1 t WHERE id =1 for UPDATE ;
SELECT * FROM  tb_1 t WHERE id =2 for UPDATE ;

同理第二個事務現針對id為2的數據上寫鎖,在針對id為1的數據上寫鎖,由此雙方循環等待,造成死鎖。

begin;
SELECT * FROM  tb_1 t WHERE id =2 for UPDATE ;
SELECT * FROM  tb_1 t WHERE id =1 for UPDATE ;

接下來我們就基于上述所說的3張表進行死鎖的問題的定位,首先我們查看INNODB_TRX可以看到我們本次SQL的食物號為567585它處于鎖等待狀態,可以看到它正在執行的SQL語句以及上鎖的信息。

然后我們到data_locks表查看當前數據庫的鎖情況,這個事務正在等待X鎖和REC_NOT_GAP鎖,說明這個事務存在死鎖:

最后再到data_lock_waits可以看到567585的事務等待567584的事務。

明確定位了兩個事務的,查看innodb 狀態信息定位到這兩個事務號的執行執行語句:

show engine innodb status;

最終,可以看到處于等待的事務567585鎖等待的事務567584所執行的SQL語句,很明顯是因為上了同一個行級鎖造成事務567585等待造成死鎖:

針對死鎖問題這個問題,我們先得說說造成死鎖的4個條件:

  • 互斥
  • 不可剝奪
  • 請求和保持條件
  • 鎖之間構成環路

所以MySQL解決死鎖的方式大抵有以下幾種:

  • 每個事務按照順序到表中上鎖(破壞環路條件)。
  • 將大事務拆小。
  • 邏輯上要求事務必須一次性取得兩張表的鎖才能操作數據。
  • 降低隔離級別,例如將RR級別降低為RC避免上間隙鎖確保降低發生死鎖的概率。
責任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關推薦

2023-02-22 07:04:05

自動機原理優化實踐

2013-04-17 10:06:55

Google GlasMirror API

2009-06-15 15:57:21

Spring工作原理

2025-03-25 10:29:52

2019-06-03 15:15:09

MySQL索引數據庫

2020-05-22 09:12:46

HTTP3網絡協議

2023-04-06 13:15:48

MySQL復制原理應用實踐

2025-01-10 09:47:43

blockSDKiOS

2019-06-04 09:26:35

UCloudUDB數據庫

2009-06-08 16:52:00

2017-04-17 15:48:15

Cinder備份實踐

2024-05-10 11:35:22

Redis延時隊列數據庫

2025-02-06 08:24:25

AQS開發Java

2013-01-09 10:34:13

OpenStackKVM

2023-06-07 15:25:19

Kafka版本日志

2013-06-06 13:10:44

HashMap無鎖

2023-10-13 13:30:00

MySQL鎖機制

2010-08-10 17:01:48

FlexJavaScript

2021-12-20 00:03:38

Webpack運行機制

2024-07-25 09:01:22

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美乱操 | 91精品亚洲 | 午夜精品久久久久久久久久久久久 | av网站免费 | 国产精品女人久久久 | 精品久久久久久久久久久久久久 | 国产成在线观看免费视频 | 97久久精品午夜一区二区 | 性色av一区| 日韩快播电影 | 91av在线免费播放 | 日韩国产中文字幕 | 99精品网 | 91视频a| 久久伊人精品一区二区三区 | 国产乱精品一区二区三区 | 99久久久久久 | 日韩色在线 | 日韩成人高清在线 | 91影片 | 久久久99国产精品免费 | 56pao在线| 亚洲欧美综合精品久久成人 | 亚洲欧洲在线视频 | 欧美 日韩 在线播放 | 日韩视频在线一区 | 亚洲精品乱码久久久久久按摩观 | 亚洲视频1区 | 日韩精品四区 | 久久精品免费看 | 亚洲视频精品在线 | 国产精品区一区二区三区 | 97伦理 | 国产丝袜一区二区三区免费视频 | 日韩免费视频一区二区 | 在线观看中文字幕dvd播放 | 亚洲欧美激情精品一区二区 | 国产精品久久久久久久久久久免费看 | 久久精品国产一区二区电影 | 国产一区影院 | 国产精品网页 |