超详细图解!【MySQL进阶篇】MySQL事务和锁
ACID 特性
在關(guān)系型數(shù)據(jù)庫管理系統(tǒng)中,一個邏輯工作單元要成為事務(wù),必須滿足這 4 個特性,即所謂的 ACID:
原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability)。
原子性
原子性:事務(wù)是一個原子操作單元,其對數(shù)據(jù)的修改,要么全都執(zhí)行,要么全都不執(zhí)行。
修改—》Buffer Pool修改—》刷盤。可能會有下面兩種情況:
- 事務(wù)提交了,如果此時Buffer Pool的臟頁沒有刷盤,如何保證修改的數(shù)據(jù)生效? Redo
- 如果事務(wù)沒提交,但是Buffer Pool的臟頁刷盤了,如何保證不該存在的數(shù)據(jù)撤銷?Undo
每一個寫事務(wù),都會修改BufferPool,從而產(chǎn)生相應(yīng)的Redo/Undo日志,在Buffer Pool 中的頁被刷到
磁盤之前,這些日志信息都會先寫入到日志文件中,如果 Buffer Pool 中的臟頁沒有刷成功,此時數(shù)據(jù)
庫掛了,那在數(shù)據(jù)庫再次啟動之后,可以通過 Redo 日志將其恢復(fù)出來,以保證臟頁寫的數(shù)據(jù)不會丟
失。如果臟頁刷新成功,此時數(shù)據(jù)庫掛了,就需要通過Undo來實現(xiàn)了。
持久性
**持久性:**指的是一個事務(wù)一旦提交,它對數(shù)據(jù)庫中數(shù)據(jù)的改變就應(yīng)該是永久性的,后續(xù)的操作或故障不
應(yīng)該對其有任何影響,不會丟失。
如下圖所示,一個“提交”動作觸發(fā)的操作有:binlog落地、發(fā)送binlog、存儲引擎提交、flush_logs,
check_point、事務(wù)提交標(biāo)記等。這些都是數(shù)據(jù)庫保證其數(shù)據(jù)完整性、持久性的手段。
MySQL的持久性也與WAL技術(shù)相關(guān),redo log在系統(tǒng)Crash重啟之類的情況時,可以修復(fù)數(shù)據(jù),從而保
障事務(wù)的持久性。通過原子性可以保證邏輯上的持久性,通過存儲引擎的數(shù)據(jù)刷盤可以保證物理上的持
久性。
隔離性
**隔離性:**指的是一個事務(wù)的執(zhí)行不能被其他事務(wù)干擾,即一個事務(wù)內(nèi)部的操作及使用的數(shù)據(jù)對其他的并
發(fā)事務(wù)是隔離的。
InnoDB 支持的隔離性有 4 種,隔離性從低到高分別為:讀未提交、讀提交、可重復(fù)讀、可串行化。鎖
和多版本控制(MVCC)技術(shù)就是用于保障隔離性的。
一致性
一致性:指的是事務(wù)開始之前和事務(wù)結(jié)束之后,數(shù)據(jù)庫的完整性限制未被破壞。一致性包括兩方面的內(nèi)
容,分別是約束一致性和數(shù)據(jù)一致性。
**約束一致性:**創(chuàng)建表結(jié)構(gòu)時所指定的外鍵、Check、唯一索引等約束,可惜在 MySQL 中不支持
Check 。
**數(shù)據(jù)一致性:**是一個綜合性的規(guī)定,因為它是由原子性、持久性、隔離性共同保證的結(jié)果,而不是
單單依賴于某一種技術(shù)。
一致性也可以理解為數(shù)據(jù)的完整性。數(shù)據(jù)的完整性是通過原子性、隔離性、持久性來保證的,而這3個
特性又是通過 Redo/Undo 來保證的。邏輯上的一致性,包括唯一索引、外鍵約束、check 約束,這屬
于業(yè)務(wù)邏輯范疇。
ACID 及它們之間的關(guān)系如下圖所示,4個特性中有3個與 WAL 有關(guān)系,都需要通過 Redo、Undo 日志
來保證等。
WAL的全稱為Write-Ahead Logging,先寫日志,再寫磁盤。
事務(wù)控制的演進(jìn)
并發(fā)事務(wù)
事務(wù)并發(fā)處理可能會帶來一些問題,比如:更新丟失、臟讀、不可重復(fù)讀、幻讀等。
-
更新丟失
當(dāng)兩個或多個事務(wù)更新同一行記錄,會產(chǎn)生更新丟失現(xiàn)象。可以分為回滾覆蓋和提交覆蓋。 -
回滾覆蓋:一個事務(wù)回滾操作,把其他事務(wù)已提交的數(shù)據(jù)給覆蓋了。
-
提交覆蓋:一個事務(wù)提交操作,把其他事務(wù)已提交的數(shù)據(jù)給覆蓋了。
-
臟讀
一個事務(wù)讀取到了另一個事務(wù)修改但未提交的數(shù)據(jù)。 -
不可重復(fù)讀
一個事務(wù)中多次讀取同一行記錄不一致,后面讀取的跟前面讀取的不一致。 -
幻讀
一個事務(wù)中多次按相同條件查詢,結(jié)果不一致。后續(xù)查詢的結(jié)果和面前查詢結(jié)果不同,多了或少了
幾行記錄。
排隊
最簡單的方法,就是完全順序執(zhí)行所有事務(wù)的數(shù)據(jù)庫操作,不需要加鎖,簡單的說就是全局排隊。序列
化執(zhí)行所有的事務(wù)單元,數(shù)據(jù)庫某個時刻只處理一個事務(wù)操作,特點是強一致性,處理性能低。
排他鎖
引入鎖之后就可以支持并發(fā)處理事務(wù),如果事務(wù)之間涉及到相同的數(shù)據(jù)項時,會使用排他鎖,或叫互斥
鎖,先進(jìn)入的事務(wù)獨占數(shù)據(jù)項以后,其他事務(wù)被阻塞,等待前面的事務(wù)釋放鎖。
注意,在整個事務(wù)1結(jié)束之前,鎖是不會被釋放的,所以,事務(wù)2必須等到事務(wù)1結(jié)束之后開始。
讀寫鎖
讀和寫操作:讀讀、寫寫、讀寫、寫讀。
讀寫鎖就是進(jìn)一步細(xì)化鎖的顆粒度,區(qū)分讀操作和寫操作,讓讀和讀之間不加鎖,這樣下面的兩個事務(wù)
就可以同時被執(zhí)行了。
讀寫鎖,可以讓讀和讀并行,而讀和寫、寫和讀、寫和寫這幾種之間還是要加排他鎖。
MVCC
多版本控制MVCC,也就是Copy on Write的思想。MVCC除了支持讀和讀并行,還支持讀和寫、寫和讀
的并行,但為了保證一致性,寫和寫是無法并行的。
在事務(wù)1開始寫操作的時候會copy一個記錄的副本,其他事務(wù)讀操作會讀取這個記錄副本,因此不會影
響其他事務(wù)對此記錄的讀取,實現(xiàn)寫和讀并行。
MVCC概念
MVCC(Multi Version Concurrency Control)被稱為多版本控制,是指在數(shù)據(jù)庫中為了實現(xiàn)高并發(fā)的
數(shù)據(jù)訪問,對數(shù)據(jù)進(jìn)行多版本處理,并通過事務(wù)的可見性來保證事務(wù)能看到自己應(yīng)該看到的數(shù)據(jù)版本。
多版本控制很巧妙地將稀缺資源的獨占互斥轉(zhuǎn)換為并發(fā),大大提高了數(shù)據(jù)庫的吞吐量及讀寫性能。
如何生成的多版本?每次事務(wù)修改操作之前,都會在Undo日志中記錄修改之前的數(shù)據(jù)狀態(tài)和事務(wù)號,
該備份記錄可以用于其他事務(wù)的讀取,也可以進(jìn)行必要時的數(shù)據(jù)回滾。
MVCC實現(xiàn)原理
MVCC最大的好處是讀不加鎖,讀寫不沖突。在讀多寫少的系統(tǒng)應(yīng)用中,讀寫不沖突是非常重要的,極
大的提升系統(tǒng)的并發(fā)性能,這也是為什么現(xiàn)階段幾乎所有的關(guān)系型數(shù)據(jù)庫都支持 MVCC 的原因,不過目
前MVCC只在 Read Commited 和 Repeatable Read 兩種隔離級別下工作。
在 MVCC 并發(fā)控制中,讀操作可以分為兩類: 快照讀(Snapshot Read)與當(dāng)前讀 (Current Read)。
- 快照讀:讀取的是記錄的快照版本(有可能是歷史版本),不用加鎖。(select)
- 當(dāng)前讀:讀取的是記錄的最新版本,并且當(dāng)前讀返回的記錄,都會加鎖,保證其他事務(wù)不會再并發(fā)
修改這條記錄。(select… for update 或lock in share mode,insert/delete/update)
為了讓大家更直觀地理解 MVCC 的實現(xiàn)原理,舉一個記錄更新的案例來講解 MVCC 中多版本的實現(xiàn)。
假設(shè) F1~F6 是表中字段的名字,1~6 是其對應(yīng)的數(shù)據(jù)。后面三個隱含字段分別對應(yīng)該行的隱含ID、事
務(wù)號和回滾指針,如下圖所示。
具體的更新過程如下:
假如一條數(shù)據(jù)是剛 INSERT 的,DB_ROW_ID 為 1,其他兩個字段為空。當(dāng)事務(wù) 1 更改該行的數(shù)據(jù)值
時,會進(jìn)行如下操作,如下圖所示。
-
用排他鎖鎖定該行;記錄 Redo log;
-
把該行修改前的值復(fù)制到 Undo log,即圖中下面的行;
-
修改當(dāng)前行的值,填寫事務(wù)編號,使回滾指針指向 Undo log 中修改前的行。
接下來事務(wù)2操作,過程與事務(wù) 1 相同,此時 Undo log 中會有兩行記錄,并且通過回滾指針連在一
起,通過當(dāng)前記錄的回滾指針回溯到該行創(chuàng)建時的初始內(nèi)容,如下圖所示。
[圖片上傳失敗…(image-24653d-1625379524706)]
MVCC已經(jīng)實現(xiàn)了讀讀、讀寫、寫讀并發(fā)處理,如果想進(jìn)一步解決寫寫沖突,可以采用下面兩種方案:
- 樂觀鎖
- 悲觀鎖
事務(wù)隔離級別
隔離級別類型
前面提到的“更新丟失”、”臟讀”、“不可重復(fù)讀”和“幻讀”等并發(fā)事務(wù)問題,其實都是數(shù)據(jù)庫一致性問題,
為了解決這些問題,MySQL數(shù)據(jù)庫是通過事務(wù)隔離級別來解決的,數(shù)據(jù)庫系統(tǒng)提供了以下 4 種事務(wù)隔
離級別供用戶選擇。
-
讀未提交
Read Uncommitted 讀未提交:解決了回滾覆蓋類型的更新丟失,但可能發(fā)生臟讀現(xiàn)象,也就是
可能讀取到其他會話中未提交事務(wù)修改的數(shù)據(jù)。 -
已提交讀
Read Committed 讀已提交:只能讀取到其他會話中已經(jīng)提交的數(shù)據(jù),解決了臟讀。但可能發(fā)生
不可重復(fù)讀現(xiàn)象,也就是可能在一個事務(wù)中兩次查詢結(jié)果不一致。 -
可重復(fù)讀
Repeatable Read 可重復(fù)讀:解決了不可重復(fù)讀,它確保同一事務(wù)的多個實例在并發(fā)讀取數(shù)據(jù)
時,會看到同樣的數(shù)據(jù)行。不過理論上會出現(xiàn)幻讀,簡單的說幻讀指的的當(dāng)用戶讀取某一范圍的數(shù)
據(jù)行時,另一個事務(wù)又在該范圍插入了新行,當(dāng)用戶在讀取該范圍的數(shù)據(jù)時會發(fā)現(xiàn)有新的幻影行。 -
可串行化
Serializable 串行化:所有的增刪改查串行執(zhí)行。它通過強制事務(wù)排序,解決相互沖突,從而解決
幻度的問題。這個級別可能導(dǎo)致大量的超時現(xiàn)象的和鎖競爭,效率低下。
數(shù)據(jù)庫的事務(wù)隔離級別越高,并發(fā)問題就越小,但是并發(fā)處理能力越差(代價)。讀未提交隔離級別最
低,并發(fā)問題多,但是并發(fā)處理能力好。以后使用時,可以根據(jù)系統(tǒng)特點來選擇一個合適的隔離級別,
比如對不可重復(fù)讀和幻讀并不敏感,更多關(guān)心數(shù)據(jù)庫并發(fā)處理能力,此時可以使用Read Commited隔
離級別。
事務(wù)隔離級別,針對Innodb引擎,支持事務(wù)的功能。像MyISAM引擎沒有關(guān)系。
事務(wù)隔離級別和鎖的關(guān)系
1)事務(wù)隔離級別是SQL92定制的標(biāo)準(zhǔn),相當(dāng)于事務(wù)并發(fā)控制的整體解決方案,本質(zhì)上是對鎖和MVCC使
用的封裝,隱藏了底層細(xì)節(jié)。
2)鎖是數(shù)據(jù)庫實現(xiàn)并發(fā)控制的基礎(chǔ),事務(wù)隔離性是采用鎖來實現(xiàn),對相應(yīng)操作加不同的鎖,就可以防
止其他事務(wù)同時對數(shù)據(jù)進(jìn)行讀寫操作。
3)對用戶來講,首先選擇使用隔離級別,當(dāng)選用的隔離級別不能解決并發(fā)問題或需求時,才有必要在
開發(fā)中手動的設(shè)置鎖。
MySQL默認(rèn)隔離級別:可重復(fù)讀
Oracle、SQLServer默認(rèn)隔離級別:讀已提交
一般使用時,建議采用默認(rèn)隔離級別,然后存在的一些并發(fā)問題,可以通過悲觀鎖、樂觀鎖等實現(xiàn)處
理。
MySQL隔離級別控制
MySQL默認(rèn)的事務(wù)隔離級別是Repeatable Read,查看MySQL當(dāng)前數(shù)據(jù)庫的事務(wù)隔離級別命令如下:
show variables like 'tx_isolation';或
select @@tx_isolation;
設(shè)置事務(wù)隔離級別可以如下命令:
鎖機制和實戰(zhàn)
鎖分類
在 MySQL中鎖有很多不同的分類。
-
從操作的粒度可分為表級鎖、行級鎖和頁級鎖。
-
表級鎖:每次操作鎖住整張表。鎖定粒度大,發(fā)生鎖沖突的概率最高,并發(fā)度最低。應(yīng)用在
MyISAM、InnoDB、BDB 等存儲引擎中。 -
行級鎖:每次操作鎖住一行數(shù)據(jù)。鎖定粒度最小,發(fā)生鎖沖突的概率最低,并發(fā)度最高。應(yīng)
用在InnoDB 存儲引擎中。 -
頁級鎖:每次鎖定相鄰的一組記錄,鎖定粒度界于表鎖和行鎖之間,開銷和加鎖時間界于表
鎖和行鎖之間,并發(fā)度一般。應(yīng)用在BDB 存儲引擎中。
-
從操作的類型可分為讀鎖和寫鎖。
-
讀鎖(S鎖):共享鎖,針對同一份數(shù)據(jù),多個讀操作可以同時進(jìn)行而不會互相影響。
-
寫鎖(X鎖):排他鎖,當(dāng)前寫操作沒有完成前,它會阻斷其他寫鎖和讀鎖。
IS鎖、IX鎖:意向讀鎖、意向?qū)戞i,屬于表級鎖,S和X主要針對行級鎖。在對表記錄添加S或X鎖之
前,會先對表添加IS或IX鎖。
S鎖:事務(wù)A對記錄添加了S鎖,可以對記錄進(jìn)行讀操作,不能做修改,其他事務(wù)可以對該記錄追加
S鎖,但是不能追加X鎖,需要追加X鎖,需要等記錄的S鎖全部釋放。
X鎖:事務(wù)A對記錄添加了X鎖,可以對記錄進(jìn)行讀和修改操作,其他事務(wù)不能對記錄做讀和修改操
作。
-
從操作的性能可分為樂觀鎖和悲觀鎖。
-
樂觀鎖:一般的實現(xiàn)方式是對記錄數(shù)據(jù)版本進(jìn)行比對,在數(shù)據(jù)更新提交的時候才會進(jìn)行沖突
檢測,如果發(fā)現(xiàn)沖突了,則提示錯誤信息。 -
悲觀鎖:在對一條數(shù)據(jù)修改的時候,為了避免同時被其他人修改,在修改數(shù)據(jù)之前先鎖定,
再修改的控制方式。共享鎖和排他鎖是悲觀鎖的不同實現(xiàn),但都屬于悲觀鎖范疇。
行鎖原理
在InnoDB引擎中,我們可以使用行鎖和表鎖,其中行鎖又分為共享鎖和排他鎖。InnoDB****行鎖是通過對
索引數(shù)據(jù)頁上的記錄加鎖實現(xiàn)的,主要實現(xiàn)算法有 3 種:Record Lock、Gap Lock 和 Next-key Lock。
-
RecordLock鎖:鎖定單個行記錄的鎖。(記錄鎖,RC、RR隔離級別都支持)
-
GapLock鎖:間隙鎖,鎖定索引記錄間隙,確保索引記錄的間隙不變。(范圍鎖,RR隔離級別支
持) -
Next-key Lock 鎖:記錄鎖和間隙鎖組合,同時鎖住數(shù)據(jù),并且鎖住數(shù)據(jù)前后范圍。(記錄鎖+范
圍鎖,RR隔離級別支持)
在RR隔離級別,InnoDB對于記錄加鎖行為都是先采用Next-Key Lock,但是當(dāng)SQL操作含有唯一索引
時,Innodb會對Next-Key Lock進(jìn)行優(yōu)化,降級為RecordLock,僅鎖住索引本身而非范圍。
select … from 語句:InnoDB引擎采用MVCC機制實現(xiàn)非阻塞讀,所以對于普通的select語句,
InnoDB不加鎖
select … from lock in share mode語句:追加了共享鎖,InnoDB會使用Next-Key Lock鎖進(jìn)行處
理,如果掃描發(fā)現(xiàn)唯一索引,可以降級為RecordLock鎖。
select … from for update語句:追加了排他鎖,InnoDB會使用Next-Key Lock鎖進(jìn)行處理,如果掃
描發(fā)現(xiàn)唯一索引,可以降級為RecordLock鎖。
update … where 語句:InnoDB會使用Next-Key Lock鎖進(jìn)行處理,如果掃描發(fā)現(xiàn)唯一索引,可以
降級為RecordLock鎖。
delete … where 語句:InnoDB會使用Next-Key Lock鎖進(jìn)行處理,如果掃描發(fā)現(xiàn)唯一索引,可以降
級為RecordLock鎖。
insert語句:InnoDB會在將要插入的那一行設(shè)置一個排他的RecordLock鎖。
下面以“update t1 set name=‘XX’ where id=10”操作為例,舉例子分析下 InnoDB 對不同索引的加鎖行
為,以RR隔離級別為例。
- 主鍵加鎖
加鎖行為:僅在id=10的主鍵索引記錄上加X鎖。
- 唯一鍵加鎖
加鎖行為:現(xiàn)在唯一索引id上加X鎖,然后在id=10的主鍵索引記錄上加X鎖。
- 非唯一鍵加鎖
加鎖行為:對滿足id=10條件的記錄和主鍵分別加X鎖,然后在(6,c)-(10,b)、(10,b)-(10,d)、(10,d)-
(11,f)范圍分別加Gap Lock。
- 無索引加鎖
加鎖行為:表里所有行和間隙都會加X鎖。(當(dāng)沒有索引時,會導(dǎo)致全表鎖定,因為InnoDB引擎
鎖機制是基于索引實現(xiàn)的記錄鎖定)。【白嫖資料】
悲觀鎖
悲觀鎖(Pessimistic Locking),是指在數(shù)據(jù)處理過程,將數(shù)據(jù)處于鎖定狀態(tài),一般使用數(shù)據(jù)庫的鎖機
制實現(xiàn)。從廣義上來講,前面提到的行鎖、表鎖、讀鎖、寫鎖、共享鎖、排他鎖等,這些都屬于悲觀鎖
范疇。
-
表級鎖
表級鎖每次操作都鎖住整張表,并發(fā)度最低。常用命令如下:
手動增加表鎖
lock table 表名稱 read|write,表名稱2 read|write;
查看表上加過的鎖
show open tables;
刪除表鎖
unlock tables;
表級讀鎖:當(dāng)前表追加read鎖,當(dāng)前連接和其他的連接都可以讀操作;但是當(dāng)前連接增刪改操作
會報錯,其他連接增刪改會被阻塞。
表級寫鎖:當(dāng)前表追加write鎖,當(dāng)前連接可以對表做增刪改查操作,其他連接對該表所有操作都
被阻塞(包括查詢)。
總結(jié):表級讀鎖會阻塞寫操作,但是不會阻塞讀操作。而寫鎖則會把讀和寫操作都阻塞。 -
共享鎖(行級鎖-讀鎖)
共享鎖又稱為讀鎖,簡稱S鎖。共享鎖就是多個事務(wù)對于同一數(shù)據(jù)可以共享一把鎖,都能訪問到數(shù)
據(jù),但是只能讀不能修改。使用共享鎖的方法是在select … lock in share mode,只適用查詢語
句。
總結(jié):事務(wù)使用了共享鎖(讀鎖),只能讀取,不能修改,修改操作被阻塞。 -
排他鎖(行級鎖-寫鎖)
排他鎖又稱為寫鎖,簡稱X鎖。排他鎖就是不能與其他鎖并存,如一個事務(wù)獲取了一個數(shù)據(jù)行的排
他鎖,其他事務(wù)就不能對該行記錄做其他操作,也不能獲取該行的鎖。【白嫖資料】
使用排他鎖的方法是在SQL末尾加上for update,innodb引擎默認(rèn)會在update,delete語句加上
for update。行級鎖的實現(xiàn)其實是依靠其對應(yīng)的索引,所以如果操作沒用到索引的查詢,那么會鎖
住全表記錄。
總結(jié):事務(wù)使用了排他鎖(寫鎖),當(dāng)前事務(wù)可以讀取和修改,其他事務(wù)不能修改,也不能獲取記錄
鎖(select… for update)。如果查詢沒有使用到索引,將會鎖住整個表記錄。
樂觀鎖
樂觀鎖是相對于悲觀鎖而言的,它不是數(shù)據(jù)庫提供的功能,需要開發(fā)者自己去實現(xiàn)。在數(shù)據(jù)庫操作時,
想法很樂觀,認(rèn)為這次的操作不會導(dǎo)致沖突,因此在數(shù)據(jù)庫操作時并不做任何的特殊處理,即不加鎖,
而是在進(jìn)行事務(wù)提交時再去判斷是否有沖突了。
樂觀鎖實現(xiàn)的關(guān)鍵點:沖突的檢測。
悲觀鎖和樂觀鎖都可以解決事務(wù)寫寫并發(fā),在應(yīng)用中可以根據(jù)并發(fā)處理能力選擇區(qū)分,比如對并發(fā)率要
求高的選擇樂觀鎖;對于并發(fā)率要求低的可以選擇悲觀鎖。
-
樂觀鎖實現(xiàn)原理
-
使用版本字段(version)
先給數(shù)據(jù)表增加一個版本(version) 字段,每操作一次,將那條記錄的版本號加 1。version
是用來查看被讀的記錄有無變化,作用是防止記錄在業(yè)務(wù)處理期間被其他事務(wù)修改。
-
使用時間戳(Timestamp)
與使用version版本字段相似,同樣需要給在數(shù)據(jù)表增加一個字段,字段類型使用timestamp
時間戳。也是在更新提交的時候檢查當(dāng)前數(shù)據(jù)庫中數(shù)據(jù)的時間戳和自己更新前取到的時間戳
進(jìn)行對比,如果一致則提交更新,否則就是版本沖突,取消操作。 -
樂觀鎖案例
下面我們使用下單過程作為案例,描述下樂觀鎖的使用。 -
第一步:查詢商品信息
select (quantity,version) from products where id=1; -
第二部:根據(jù)商品信息生成訂單
insert into orders ...insert into items ... -
第三部:修改商品庫存
update products set quantity=quantity-1,version=version+1where id=1 and version=#{version};
除了自己手動實現(xiàn)樂觀鎖之外,許多數(shù)據(jù)庫訪問框架也封裝了樂觀鎖的實現(xiàn),比如
hibernate框架。MyBatis框架大家可以使用OptimisticLocker插件來擴(kuò)展。【白嫖資料】
死鎖與解決方案
下面介紹幾種常見的死鎖現(xiàn)象和解決方案:
- 表鎖死鎖
產(chǎn)生原因:
用戶A訪問表A(鎖住了表A),然后又訪問表B;另一個用戶B訪問表B(鎖住了表B),然后企圖
訪問表A;這時用戶A由于用戶B已經(jīng)鎖住表B,它必須等待用戶B釋放表B才能繼續(xù),同樣用戶B要
等用戶A釋放表A才能繼續(xù),這就死鎖就產(chǎn)生了。
用戶A–》A表(表鎖)–》B表(表鎖)
用戶B–》B表(表鎖)–》A表(表鎖)
解決方案:
這種死鎖比較常見,是由于程序的BUG產(chǎn)生的,除了調(diào)整的程序的邏輯沒有其它的辦法。仔細(xì)分
析程序的邏輯,對于數(shù)據(jù)庫的多表操作時,盡量按照相同的順序進(jìn)行處理,盡量避免同時鎖定兩個
資源,如操作A和B兩張表時,總是按先A后B的順序處理, 必須同時鎖定兩個資源時,要保證在任
何時刻都應(yīng)該按照相同的順序來鎖定資源。
- 行級鎖死鎖
產(chǎn)生原因1:
如果在事務(wù)中執(zhí)行了一條沒有索引條件的查詢,引發(fā)全表掃描,把行級鎖上升為全表記錄鎖定(等
價于表級鎖),多個這樣的事務(wù)執(zhí)行后,就很容易產(chǎn)生死鎖和阻塞,最終應(yīng)用系統(tǒng)會越來越慢,發(fā)
生阻塞或死鎖。
解決方案1:
SQL語句中不要使用太復(fù)雜的關(guān)聯(lián)多表的查詢;使用explain“執(zhí)行計劃"對SQL語句進(jìn)行分析,對于
有全表掃描和全表鎖定的SQL語句,建立相應(yīng)的索引進(jìn)行優(yōu)化。
產(chǎn)生原因2:
兩個事務(wù)分別想拿到對方持有的鎖,互相等待,于是產(chǎn)生死鎖。
解決方案2:
-
在同一個事務(wù)中,盡可能做到一次鎖定所需要的所有資源【白嫖資料】
-
按照id對資源排序,然后按順序進(jìn)行處理
-
共享鎖轉(zhuǎn)換為排他鎖
產(chǎn)生原因:
事務(wù)A 查詢一條紀(jì)錄,然后更新該條紀(jì)錄;此時事務(wù)B 也更新該條紀(jì)錄,這時事務(wù)B 的排他鎖由于
事務(wù)A 有共享鎖,必須等A 釋放共享鎖后才可以獲取,只能排隊等待。事務(wù)A 再執(zhí)行更新操作時,
此處發(fā)生死鎖,因為事務(wù)A 需要排他鎖來做更新操作。但是,無法授予該鎖請求,因為事務(wù)B 已經(jīng)
有一個排他鎖請求,并且正在等待事務(wù)A 釋放其共享鎖。
事務(wù)A: select * from dept where deptno=1 lock in share mode; //共享鎖,1update dept set dname='java' where deptno=1;//排他鎖,3
事務(wù)B: update dept set dname='Java' where deptno=1;//由于1有共享鎖,沒法獲取排他鎖,需等待,2
解決方案:
-
對于按鈕等控件,點擊立刻失效,不讓用戶重復(fù)點擊,避免引發(fā)同時對同一條記錄多次操
作; -
使用樂觀鎖進(jìn)行控制。樂觀鎖機制避免了長事務(wù)中的數(shù)據(jù)庫加鎖開銷,大大提升了大并發(fā)量
下的系統(tǒng)性能。需要注意的是,由于樂觀鎖機制是在我們的系統(tǒng)中實現(xiàn),來自外部系統(tǒng)的用
戶更新操作不受我們系統(tǒng)的控制,因此可能會造成臟數(shù)據(jù)被更新到數(shù)據(jù)庫中; -
死鎖排查
MySQL提供了幾個與鎖有關(guān)的參數(shù)和命令,可以輔助我們優(yōu)化鎖操作,減少死鎖發(fā)生。 -
查看死鎖日志
通過show engine innodb status\G命令查看近期死鎖日志信息。
使用方法:1、查看近期死鎖日志信息;2、使用explain查看下SQL執(zhí)行計劃 -
查看鎖狀態(tài)變量
通過show status like’innodb_row_lock%‘命令檢查狀態(tài)變量,分析系統(tǒng)中的行鎖的爭奪
情況 -
Innodb_row_lock_current_waits:當(dāng)前正在等待鎖的數(shù)量
-
Innodb_row_lock_time:從系統(tǒng)啟動到現(xiàn)在鎖定總時間長度
-
Innodb_row_lock_time_avg: 每次等待鎖的平均時間
-
Innodb_row_lock_time_max:從系統(tǒng)啟動到現(xiàn)在等待最長的一次鎖的時間
-
Innodb_row_lock_waits:系統(tǒng)啟動后到現(xiàn)在總共等待的次數(shù)
如果等待次數(shù)高,而且每次等待時間長,需要分析系統(tǒng)中為什么會有如此多的等待,然后著
手定制優(yōu)化。
最后,祝大家早日學(xué)有所成,拿到滿意offer
總結(jié)
以上是生活随笔為你收集整理的超详细图解!【MySQL进阶篇】MySQL事务和锁的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 股票做t什么意思?
- 下一篇: 超详细图解!【MySQL进阶篇】MySQ