您好,登錄后才能下訂單哦!
本篇內容主要講解“MySQL中的鎖可以分成幾類”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“MySQL中的鎖可以分成幾類”吧!
根據加鎖的范圍,MySQL里面的鎖大致可以分成全局鎖、表級鎖和行鎖三類
全局鎖就是對整個數據庫實例加鎖。MySQL提供了一個加全局讀鎖的方法,命令是Flush tables with read lock
。當需要讓整個庫處于只讀狀態的時候,可以使用這個命令,之后其他線程的以下語句會被阻塞:數據更新語句(數據的增刪改)、數據定義語句(包括建表、修改表結構等)和更新類事務的提交語句。【相關推薦:mysql教程(視頻)】
全局鎖的典型使用場景是,做全庫邏輯備份。也就是把整庫每個表都select出來存成文本
但是讓整個庫都只讀,可能出現以下問題:
如果在主庫上備份,那么在備份期間都不能執行更新,業務基本上就得停擺
如果在從庫上備份,那么在備份期間從庫不能執行主庫同步過來的binlog,會導致主從延遲
在可重復讀隔離級別下開啟一個事務能夠拿到一致性視圖
官方自帶的邏輯備份工具是mysqldump。當mysqldump使用參數–single-transaction的時候,導數據之前就會啟動一個事務,來確保拿到一致性視圖。而由于MVCC的支持,這個過程中數據是可以正常更新的。single-transaction只適用于所有的表使用事務引擎的庫
1.既然要全庫只讀,為什么不使用set global readonly=true
的方式?
在有些系統中,readonly的值會被用來做其他邏輯,比如用來判斷一個庫是主庫還是備庫。因此修改global變量的方式影響面更大
在異常處理機制上有差異。如果執行Flush tables with read lock命令之后由于客戶端發生異常斷開,那么MySQL會自動釋放這個全局鎖,整個庫回到可以正常更新的狀態。而將整個庫設置為readonly之后,如果客戶端發生異常,則數據庫會一直保持readonly狀態,這樣會導致整個庫長時間處于不可寫狀態,風險較高
MySQL里面表級別的鎖有兩種:一種是表鎖,一種是元數據鎖(meta data lock,MDL)
表鎖的語法是lock tables … read/write。可以用unlock tables主動釋放鎖,也可以在客戶端斷開的時候自動釋放。lock tables語法除了會限制別的線程的讀寫外,也限定了本線程接下來的操作對象
如果在某個線程A中執行lock tables t1 read,t2 wirte;
這個語句,則其他線程寫t1、讀寫t2的語句都會被阻塞。同時,線程A在執行unlock tables之前,也只能執行讀t1、讀寫t2的操作。連寫t1都不允許
另一類表級的鎖是MDL。MDL不需要顯式使用,在訪問一個表的時候會被自動加上。MDL的作用是,保證讀寫的正確性。如果一個查詢正在遍歷一個表中的數據,而執行期間另一個線程對這個表結構做了變更,刪了一列,那么查詢線程拿到的結果跟表結構對不上,肯定不行
在MySQL5.5版本引入了MDL,當對一個表做增刪改查操作的時候,加MDL讀鎖;當要對表做結構變更操作的時候,加MDL寫鎖
讀鎖之間不互斥,因此可以有多個線程同時對一張表增刪改查
讀寫鎖之間、寫鎖之間是互斥的,用來保證變更表結構操作的安全性。因此,如果有兩個線程要同時給一個表加字段,其中一個要等另一個執行完才能開始執行
給一個表加字段,或者修改字段,或者加索引,需要掃描全表的數據。在對大表操作的時候,需要特別小心,以免對線上服務造成影響
session A先啟動,這時候會對表t加一個MDL讀鎖。由于session B需要的也是MDL讀鎖,因此可以正常執行。之后sesession C會被blocked,是因為session A的MDL讀鎖還沒有釋放,而session C需要MDL寫鎖,因此只能被阻塞。如果只有session C自己被阻塞還沒什么關系,但是之后所有要在表t上新申請MDL讀鎖的請求也會被session C阻塞。所有對表的增刪改查操作都需要先申請MDL讀鎖,就都被鎖住,等于這個表現在完全不可讀寫了
事務中的MDL鎖,在語句執行開始時申請,但是語句結束后并不會馬上釋放,而會等到整個事務提交后再釋放
1.如果安全地給小表加字段?
首先要解決長事務,事務不提交,就會一直占著DML鎖。在MySQL的information_schema庫的innodb_trx表中,可以查到當前執行的事務。如果要做DDL變更的表剛好有長事務在執行,要考慮先暫停DDL,或者kill掉這個長事務
2.如果要變更的表是一個熱點表,雖然數據量不大,但是上面的請求很頻繁,而又不得不加個字段,該怎么做?
在alter table語句里面設定等待時間,如果在這個指定的等待時間里面能夠拿到MDL寫鎖最好,拿不到也不要阻塞后面的業務語句,先放棄。之后再通過重試命令重復這個過程
MySQL的行鎖是在引擎層由各個引擎自己實現的。但不是所有的引擎都支持行鎖,比如MyISAM引擎就不支持行鎖
行鎖就是針對數據表中行記錄的鎖。比如事務A更新了一行,而這時候事務B也要更新同一行,則必須等事務A的操作完成后才能進行更新
事務A持有的兩個記錄的行鎖都是在commit的時候才釋放的,事務B的update語句會被阻塞,直到事務A執行commit之后,事務B才能繼續執行
在InnoDB事務中,行鎖是在需要的時候才加上的,但并不是不需要了就立刻釋放,而是要等到事務結束時才釋放。這個就是兩階段鎖協議
如果事務中需要鎖多個行,要把最可能造成鎖沖突、最可能影響并發度的鎖盡量往后放
假設要實現一個電影票在線交易業務,顧客A要在影院B購買電影票。業務需要涉及到以下操作:
1.從顧客A賬戶余額中扣除電影票價
2.給影院B的賬戶余額增加這張電影票價
3.記錄一條交易日志
為了保證交易的原子性,要把這三個操作放在一個事務中。如何安排這三個語句在事務中的順序呢?
如果同時有另外一個顧客C要在影院B買票,那么這兩個事務沖突的部分就是語句2了。因為它們要更新同一個影院賬戶的余額,需要修改同一行數據。根據兩階段鎖協議,所有的操作需要的行鎖都是在事務提交的時候才釋放的。所以,如果把語句2安排在最后,比如按照3、1、2這樣的順序,那么影院賬戶余額這一行的鎖時間就最少。這就最大程度地減少了事務之間的鎖等待,提升了并發度
在并發系統中不同線程出現循環資源依賴,涉及的線程都在等待別的線程釋放資源時,就會導致這幾個線程都進入無限等待的狀態,稱為死鎖
事務A在等待事務B釋放id=2的行鎖,而事務B在等待事務A釋放id=1的行鎖。事務A和事務B在互相等待對方的資源釋放,就是進入了死鎖狀態。當出現死鎖以后,有兩種策略:
一種策略是,直接進入等待,直到超時。這個超時時間可以通過參數innodb_lock_wait_timeout來設置
另一種策略是,發起死鎖檢測,發現死鎖后,主動回滾死鎖鏈條中的某一個事務,讓其他事務得以繼續執行。將參數innodb_deadlock_detect設置為on,表示開啟這個邏輯
在InnoDB中,innodb_lock_wait_timeout的默認值是50s,意味著如果采用第一個策略,當出現死鎖以后,第一個被鎖住的線程要過50s才會超時退出,然后其他線程才有可能繼續執行。對于在線服務來說,這個等待時間往往是無法接受的
正常情況下還是要采用主動死鎖檢查策略,而且innodb_deadlock_detect的默認值本身就是on。主動死鎖監測在發生死鎖的時候,是能夠快速發現并進行處理的,但是它有額外負擔的。每當一個事務被鎖的時候,就要看看它所依賴的線程有沒有被別人鎖住,如此循環,最后判斷是否出現了循環等待,也就是死鎖
如果所有事務都要更新同一行的場景,每個新來的被堵住的線程都要判斷會不會由于自己的加入導致死鎖,這是一個時間復雜度是O(n)的操作
怎么解決由這種熱點行更新導致的性能問題?
1.如果確保這個業務一定不會出現死鎖,可以臨時把死鎖檢測關掉
2.控制并發度
3.將一行改成邏輯上的多行來減少鎖沖突。以影院賬戶為例,可以考慮放在多條記錄上,比如10個記錄,影院的賬戶總額等于這10個記錄的值的總和。這樣每次要給影院賬戶加金額的時候,隨機選其中一條記錄來加。這樣每次沖突概率變成員原來的1/10,可以減少鎖等待個數,也就減少了死鎖檢測的CPU消耗
構造一個表,這個表有兩個字段id和c,并且在里面插入了10萬行記錄
CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB; CREATE DEFINER=`root`@`%` PROCEDURE `idata`() BEGIN declare i int; set i=1; while(i<=100000) do insert into t values(i,i); set i=i+1; end while; END
select * from t3 where id=1;
查詢結果長時間不返回,使用show processlist命令,查看當前語句處于什么狀態
1)、等MDL鎖
如下圖所示,使用show processlist;
命令查看Waiting for table metadata lock的示意圖
這個狀態表示現在有一個線程正在表t上請求或者持有MDL寫鎖,把select語句堵住了
場景復現:
sessionA通過lock table命令持有表t的MDL寫鎖,而sessionB的查詢需要獲取MDL讀鎖。所以,sessionB進入等待狀態
這類問題的處理方式,就是找到誰持有MDL寫鎖,然后把它kill掉。但是由于show processlist的結果里,sessionA的Command列是Sleep,導致查找起來很不方便,可以通過查詢sys.schema_table_lock_waits這張表直接找出造成阻塞的process id,把這個連接kill命令斷開即可(MySQL啟動時需要設置performance_schema=on,相比于設置為off會有10%左右的性能損失)
select blocking_pid from sys.schema_table_lock_waits;
2)、等flush
在表t上執行如下的SQL語句:
select * from information_schema.processlist where id=1;
查出來某個線程狀態為Waiting for table flush
這個狀態表示的是,現在有一個線程政要對表t做flush操作。MySQL里面對表做flush操作的用法,一般有以下兩個:
flush tables t with read lock;flush tables with read lock;
這兩個flush語句,如果指定表t的話,代表的是只關閉表t;如果沒有指定具體的表名,則表示關閉MySQL里所有打開的表
但是正常情況下這兩個語句執行起來都很快,除非它們被別的線程堵住了
所以,出現Waiting for table flush狀態的可能情況是:有一個flush tables命令被別的語句堵住了,然后它有堵住了select語句
場景復現:
sessionA中,每行調用一次sleep(1),這樣這個語句默認要執行10萬秒,在這期間表t一直是被sessionA打開著。然后,sessionB的flush tables t再去關閉表t,就需要等sessionA的查詢結束。這樣sessionC要再次查詢的話,就會被flush命令堵住了
3)、等行鎖
select * from t where id=1 lock in share mode;
由于訪問id=1這個記錄時要加讀鎖,如果這時候已經有一個事務在這行記錄上持有一個寫鎖,select語句就會被堵住
場景復現:
sessionA啟動了事務,占有寫鎖,還不提交,是導致sessionB被堵住的原因
sessionA先用start transaction with consistent snapshot命令開啟一個事務,建立事務的一致性讀(又稱為快照讀。使用的是MVCC機制讀取undo log中的已經提交的數據。所以它的讀取是非阻塞的),之后sessionB執行update語句
sessionB執行完100萬次update語句后,生成100萬個回滾日志
帶lock in share mode的語句是當前讀,因此會直接讀到1000001這個結果,速度很快;而select * from t where id=1這個語句是一致性讀,因此需要從1000001開始,依次執行undo log,執行了100萬次以后,才將1這個結果返回
建表和初始化語句如下:
CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `c` (`c`) ) ENGINE=InnoDB; insert into t values(0,0,0),(5,5,5), (10,10,10),(15,15,15),(20,20,20),(25,25,25);
這個表除了主鍵id外,還有一個索引c
為了解決幻讀問題,InnoDB引入了間隙鎖,鎖的就是兩個值之間的空隙
當執行select * from t where d=5 for update
的時候,就不止是給數據庫中已有的6個記錄加上了行鎖,還同時加了7個間隙鎖。這樣就確保了無法再插入新的記錄
行鎖分成讀鎖和寫鎖
跟間隙鎖存在沖突關系的是往這個間隙中插入一個記錄這個操作。間隙鎖之間不存在沖突關系
這里sessionB并不會被堵住。因為表t里面并沒有c=7會這個記錄,因此sessionA加的是間隙鎖(5,10)。而sessionB也是在這個間隙加的間隙鎖。它們用共同的目標,保護這個間隙,不允許插入值。但它們之間是不沖突的
間隙鎖和行鎖合稱next-key lock,每個next-key lock是前開后閉區間。表t初始化以后,如果用select * from t for update
要把整個表所有記錄鎖起來,就形成了7個next-key lock,分別是(-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +supremum]。因為+∞是開區間,在實現上,InnoDB給每個索引加了一個不存在的最大值supremum,這樣才符合都是前開后閉區間
間隙鎖和next-key lock的引入,解決了幻讀的問題,但同時也帶來了一些困擾
間隙鎖導致的死鎖:
1.sessionA執行select … for update語句,由于id=9這一行并不存在,因此會加上間隙鎖(5,10)
2.sessionB執行select … for update語句,同樣會加上間隙鎖(5,10),間隙鎖之間不會沖突
3.sessionB試圖插入一行(9,9,9),被sessionA的間隙鎖擋住了,只好進入等待
4.sessionA試圖插入一行(9,9,9),被sessionB的間隙鎖擋住了
兩個session進入互相等待狀態,形成了死鎖
間隙鎖的引入可能會導致同樣的語句鎖住更大的范圍,這其實是影響并發度的
在讀提交隔離級別下,不存在間隙鎖
表t的建表語句和初始化語句如下:
CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `c` (`c`) ) ENGINE=InnoDB; insert into t values(0,0,0),(5,5,5), (10,10,10),(15,15,15),(20,20,20),(25,25,25);
原則1:加鎖的基本單位是next-key lock,next-key lock是前開后閉區間
原則2:查找過程中訪問到的對象才會加鎖
優化1:索引上的等值查詢,給唯一索引加鎖的時候,next-key lock退化為行鎖
優化2:索引上的等值查詢,向右遍歷時且最后一個值不滿足等值條件的時候,next-key lock退化為間隙鎖
一個bug:唯一索引上的范圍查詢會訪問到不滿足條件的第一個值為止
這個規則只限于MySQL5.x系列<=5.7.24,8.0系列<=8.0.13
1.由于表t中沒有id=7的記錄,根據原則1,加鎖單位是next-key lock,sessionA加鎖范圍就是(5,10]
2.根據優化2,這是一個等值查詢(id=7),而id=10不滿足查詢條件,next-key lock退化成間隙鎖,因此最終加鎖的范圍是(5,10)
所以,sessionB要往這個間隙里面插入id=8的記錄會被鎖住,但是sessionC修改id=10這行是可以的
1.根據原則1,加鎖單位是next-key lock,因此會給(0,5]加上next-key lock
2.c是普通索引,因此訪問c=5這一條記錄是不能馬上停下來的,需要向右遍歷,查到c=10才放棄。根據原則2,訪問到的都要加鎖,因此要給(5,10]加next-key lock
3.根據優化2,等值判斷,向右遍歷,最后一個值不滿足c=5這個等值條件,因此退化成間隙鎖(5,10)
4.根據原則2,只有訪問到的對象才會加鎖,這個查詢使用覆蓋索引,并不需要訪問主鍵索引,所以主鍵索引上沒有任何鎖,這就是為什么sessionB的update語句可以執行完成
鎖是加在索引上的,在這個例子中,lock in share mode只鎖覆蓋索引,但是如果是for update,系統會認為你接下來要更新數據,因此會順便給主鍵索引上滿足條件的行加上行鎖,這樣的話sessionB的update語句會被阻塞住。如果你要用 lock in share mode 來給行加讀鎖避免數據被更新的話,就必須得繞過覆蓋索引的優化,在查詢字段中加入索引中不存在的字段
1.開始執行的時候,要找到第一個id=10的行,因此本該是next-key lock(5,10]。根據優化1,主鍵id上的等值條件,退化成行鎖,只加了id=10這一行的行鎖
2.范圍查詢就往后繼續找,找到id=15這一行停下來,因此需要加next-key lock(10,15]
所以,sessionA這時候鎖的范圍就是主鍵索引上,行鎖id=10和next-key lock(10,15]
這次sessionA用字段c來判斷,加鎖規則跟案例三唯一的不同是:在第一次用c=10定位記錄的時候,索引c上加上(5,10]這個next-key lock后,由于索引c是非唯一索引,沒有優化規則,因此最終sessionA加的鎖是索引c上的(5,10]和(10,15]這兩個next-key lock
sessionA是一個范圍查詢,按照原則1的話,應該是索引id上只加(10,15]這個next-key lock,并且因為id是唯一鍵,所以循環判斷到id=15這一行就應該停止了
但是實現上,InnoDB會掃描到第一個不滿足條件的行為止,也就是id=20。而且由于這是個范圍掃描,因此索引id上的(15,20]這個next-key lock也會被鎖上
所以,sessionB要更新id=20這一行是會被鎖住的。同樣地,sessionC要插入id=16的一行,也會被鎖住
insert into t values(30,10,30);
新插入的這一行c=10,現在表里有兩個c=10的行。雖然有兩個c=10,但是它們的主鍵值id是不同的,因此這兩個c=10的記錄之間也是有間隙的
sessionA在遍歷的時候,先訪問第一個c=10的記錄。根據原則1,這里加的是(c=5,id=5)到(c=10,id=10)這個next-key lock。然后sessionA向右查找,直到碰到(c=15,id=15)這一行,循環才結束。根據優化2,這是一個等值查詢,向右查找到了不滿足條件的行,所以會退化成(c=10,id=10)到(c=15,id=15)的間隙鎖
也就是說,這個delete語句在索引c上的加鎖范圍,就是下圖中藍色區域覆蓋的部分,這個藍色區域左右兩邊都是虛線,表示開區間
加了limit 2的限制,因此在遍歷到(c=10,id=30)這一行之后,滿足條件的語句已經有兩條,循環就結束了。因此,索引c上的加鎖范圍就變成了從(c=5,id=5)到(c=10,id=30)這個前開后閉區間,如下圖所示:
再刪除數據的時候盡量加limit,這樣不僅可以控制刪除數據的條數,讓操作更安全,還可以減小加鎖的范圍
1.sessionA啟動事務后執行查詢語句加lock in share mode,在索引c上加了next-key lock(5,10]和間隙鎖(10,15)
2.sessionB的update語句也要在索引c上加next-key lock(5,10],進入鎖等待
3.然后sessionA要再插入(8,8,8)這一行,被sessionB的間隙鎖鎖住。由于出現了死鎖,InnoDB讓sessionB回滾
sessionB的加next-key lock(5,10]操作,實際上分成了兩步,先是加(5,10)間隙鎖,加鎖成功;然后加c=10的行鎖,這時候才被鎖住的
表t的建表語句和初始化語句如下:
CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `c` (`c`) ) ENGINE=InnoDB; insert into t values(0,0,0),(5,5,5), (10,10,10),(15,15,15),(20,20,20),(25,25,25);
begin; select * from t where id>9 and id<12 order by id desc for update;
利用上面的加鎖規則,這個語句的加鎖范圍是主鍵索引上的(0,5]、(5,10]和(10,15)。加鎖單位是next-key lock,這里用到了優化2,即索引上的等值查詢,向右遍歷的時候id=15不滿足條件,所以next-key lock退化為了間隙鎖(10,15)
1.首先這個查詢語句的語義是order by id desc,要拿到滿足條件的所有行,優化器必須先找到第一個id<12的值
2.這個過程是通過索引樹的搜索過程得到的,在引擎內部,其實是要找到id=12的這個值,只是最終沒找到,但找到了(10,15)這個間隙
3.然后根據order by id desc,再向左遍歷,在遍歷過程中,就不是等值查詢了,會掃描到id=5這一行,所以會加一個next-key lock (0,5]
在執行過程中,通過樹搜索的方式定位記錄的時候,用的是等值查詢的方法
begin; select id from t where c in(5,20,10) lock in share mode;
這條in語句使用了索引c并且rows=3,說明這三個值都是通過B+樹搜索定位的
在查找c=5的時候,先鎖住了(0,5]。但是因為c不是唯一索引,為了確認還有沒有別的記錄c=5,就要向右遍歷,找到c=10確認沒有了,這個過程滿足優化2,所以加了間隙鎖(5,10)。執行c=10會這個邏輯的時候,加鎖的范圍是(5,10]和(10,15),執行c=20這個邏輯的時候,加鎖的范圍是(15,20]和(20,25)
這條語句在索引c上加的三個記錄鎖的順序是:先加c=5的記錄鎖,再加c=10的記錄鎖,最后加c=20的記錄鎖
select id from t where c in(5,20,10) order by c desc for update;
由于語句里面是order by c desc,這三個記錄鎖的加鎖順序是先鎖c=20,然后c=10,最后是c=5。這兩條語句要加鎖相同的資源,但是加鎖順序相反。當這兩條語句并發執行的時候,就可能出現死鎖
表t和t2的表結構、初始化數據語句如下:
CREATE TABLE `t` ( `id` int(11) NOT NULL AUTO_INCREMENT, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `c` (`c`) ) ENGINE=InnoDB; insert into t values(null, 1,1); insert into t values(null, 2,2); insert into t values(null, 3,3); insert into t values(null, 4,4); create table t2 like t;
在可重復讀隔離級別下,binlog_format=statement時執行下面這個語句時,需要對表t的所有行和間隙加鎖
insert into t2(c,d) select c,d from t;
要往表t2中插入一行數據,這一行的c值是表t中c值的最大值加1,SQL語句如下:
insert into t2(c,d) (select c+1, d from t force index(c) order by c desc limit 1);
這個語句的加鎖范圍,就是表t索引c上的(3,4]和(4,supermum]這兩個next-key lock,以及主鍵索引上id=4這一行
執行流程是從表t中按照索引c倒序嗎,掃描第一行,拿到結果寫入到表t2中,因此整條語句的掃描行數是1
但如果要把這一行的數據插入到表t中的話:
insert into t(c,d) (select c+1, d from t force index(c) order by c desc limit 1);
explain結果中的Extra字段中Using temporary字段,表示這個語句用到了臨時表
執行流程如下:
1.創建臨時表,表里有兩個字段c和d
2.按照索引c掃描表t,依次取c=4、3、2、1,然后回表,讀到c和d的值寫入臨時表
3.由于語義里面有limit 1,所以只取了臨時表的第一行,再插入到表t中
這個語句會導致在表t上做全表掃描,并且會給索引c上的所有間隙都加上共享的next-key lock。所以,這個語句執行期間,其他事務不能在這個表上插入數據
需要臨時表是因為這類一邊遍歷數據,一邊更新數據的情況,如果讀出來的數據直接寫回原表,就可能在遍歷過程中,讀到剛剛插入的記錄,新插入的記錄如果參與計算邏輯,就跟語義不符
sessionA執行的insert語句,發生唯一鍵沖突的時候,并不只是簡單地報錯返回,還在沖突的索引上加了鎖,sessionA持有索引c上的(5,10]共享next-key lock(讀鎖)
在sessionA執行rollback語句回滾的時候,sessionC幾乎同時發現死鎖并返回
1.在T1時刻,啟動sessionA,并執行insert語句,此時在索引c的c=5上加了記錄鎖。這個索引是唯一索引,因此退化為記錄鎖
2.在T2時刻,sessionA回滾。這時候,sessionB和sessionC都試圖繼續執行插入操作,都要加上寫鎖。兩個session都要等待對方的行鎖,所以就出現了死鎖
上面這個例子是主鍵沖突后直接報錯,如果改寫成
insert into t values(11,10,10) on duplicate key update d=100;
就會給索引c上(5,10]加一個排他的next-key lock(寫鎖)
insert into … on duplicate key update的語義邏輯是,插入一行數據,如果碰到唯一鍵約束,就繼續執行后面的更新語句。如果有多個列違反了唯一性索引,就會按照索引的順序,修改跟第一個索引沖突的行
表t里面已經有了(1,1,1)和(2,2,2)這兩行,執行這個語句效果如下:
主鍵id是先判斷的,MySQL認為這個語句跟id=2這一行沖突,所以修改的是id=2的行
到此,相信大家對“MySQL中的鎖可以分成幾類”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。