面試官:談?wù)凪ySQL鎖機制
MySQL 的鎖機制是保證數(shù)據(jù)庫并發(fā)訪問時數(shù)據(jù)一致性和完整性的核心手段。
從鎖的粒度來說,MySQL 支持表級鎖、行級鎖和頁級鎖,其中 InnoDB 主要使用行級鎖來提高并發(fā)性能。
從鎖的類型角度,MySQL 分為共享鎖(S 鎖,讀鎖)和排他鎖(X 鎖,寫鎖)。共享鎖允許多個事務(wù)同時讀取同一資源,但不允許寫入;排他鎖則是獨占式的,持有排他鎖的事務(wù)可以讀寫數(shù)據(jù),其他事務(wù)無法獲取任何鎖。
在實際應(yīng)用中,InnoDB 引擎還實現(xiàn)了意向鎖(IS 鎖和 IX 鎖),這是表級鎖,是一個標識,用于表明事務(wù)稍后要獲取的行鎖類型,主要是為了提高加表級鎖時的效率,避免逐行檢查。
為了解決幻讀問題,InnoDB 引入了間隙鎖(Gap Lock)和臨鍵鎖(Next-Key Lock)。間隙鎖鎖定索引記錄之間的間隙,防止其他事務(wù)在該間隙插入數(shù)據(jù);臨鍵鎖是行鎖和間隙鎖的組合,鎖定記錄本身及其前面的間隙。
從鎖的實現(xiàn)層面,InnoDB 采用兩階段鎖協(xié)議,事務(wù)執(zhí)行過程中隨時可以加鎖,但所有鎖只有在事務(wù)提交或回滾時才會統(tǒng)一釋放。這個機制雖然簡單,但可能導(dǎo)致死鎖問題,MySQL 通過死鎖檢測和超時機制來處理死鎖。
另外,還有樂觀鎖和悲觀鎖的概念。悲觀鎖認為并發(fā)沖突概率高,每次操作都加鎖;樂觀鎖則相反,通過版本號或 CAS 機制在提交時檢查沖突。MySQL 的鎖機制本質(zhì)上是悲觀鎖的實現(xiàn),而樂觀鎖需要在應(yīng)用層實現(xiàn)。
一、鎖的基本概念:為什么需要鎖?
我們先理解一個根本問題:為什么數(shù)據(jù)庫需要鎖?
想象一個電商場景:雙十一零點,成千上萬的用戶同時搶購一件商品,庫存只有 100 件。如果沒有鎖機制,可能出現(xiàn)這樣糟糕的情況:
張三查詢庫存,看到還有 1 件,準備下單。與此同時,李四也查詢庫存,也看到還有 1 件。然后兩人幾乎同時提交訂單,系統(tǒng)扣減庫存。結(jié)果本來只有 1 件商品,卻賣出去了 2 件,出現(xiàn)了超賣的問題,這就是典型的并發(fā)問題。
鎖機制的本質(zhì)就是通過串行化某些操作來保證數(shù)據(jù)的一致性。當張三正在處理庫存扣減時,系統(tǒng)會給這條庫存記錄加鎖,李四只能等待,直到張三完成操作釋放鎖后,李四才能繼續(xù)。
但是,鎖的代價是降低并發(fā)性能。如果所有操作都串行化,那數(shù)據(jù)庫的并發(fā)能力就太差了。因此,MySQL 就設(shè)計了一套精細化的鎖機制,在保證數(shù)據(jù)一致性的前提下,盡可能的去提高并發(fā)性能。
二、鎖的粒度
MySQL 支持多種鎖粒度
2.1 表級鎖
表級鎖是最簡單的鎖實現(xiàn)方式,直接鎖定整張表。當一個事務(wù)獲取表級寫鎖時,其他事務(wù)無法讀寫該表;獲取表級讀鎖時,其他事務(wù)可以讀但不能寫。
表級鎖的優(yōu)點是開銷小,加鎖快,不會出現(xiàn)死鎖。但缺點也很明顯:鎖的粒度太大,并發(fā)度低。比如一個表有 100 萬條記錄,某個事務(wù)只想更新其中 1 條,卻要鎖住整張表,其他 99 萬 9999 條記錄都被"連累"了。
MySQL中的MyISAM 存儲引擎就是使用表級鎖。這也是為什么 MyISAM 不適合高并發(fā)寫入場景的根本原因。
2.2 行級鎖
行級鎖是 InnoDB 的核心特性,它只鎖定需要操作的數(shù)據(jù)行,其他行不受影響。還是剛才的例子,如果使用行級鎖,一個事務(wù)更新第 1 條記錄時,另一個事務(wù)完全可以同時更新第 2 條記錄,互不干擾。
行級鎖的并發(fā)度高,但開銷也更大。每加一個鎖都需要消耗內(nèi)存,維護鎖的數(shù)據(jù)結(jié)構(gòu)也需要額外的 CPU 開銷。而且,行級鎖可能導(dǎo)致死鎖問題。
2.3 頁級鎖
頁級鎖是 BDB 存儲引擎使用的鎖機制,鎖定的是數(shù)據(jù)頁(通常是 16KB)。它介于表級鎖和行級鎖之間,既不會像表級鎖那樣粒度過粗,也不會像行級鎖那樣開銷過大。但頁級鎖的實現(xiàn)復(fù)雜度較高,而且可能出現(xiàn)鎖沖突的概率介于二者之間。
由于 BDB 引擎已經(jīng)不再主流,BDB存儲引擎在MySQL 5.1版本后被逐步棄用。
三、鎖的類型
在確定了鎖的粒度后,我們還需要區(qū)分鎖的類型。InnoDB 主要使用兩種基本鎖類型:
3.1 共享鎖(Shared Lock,S 鎖)
共享鎖,顧名思義,是可以"共享"的鎖。當一個事務(wù)對某行數(shù)據(jù)加上共享鎖后,其他事務(wù)也可以對該行加共享鎖,大家一起讀取數(shù)據(jù)。但是,任何事務(wù)都不能對該行加排他鎖,也就是不能修改數(shù)據(jù)。
比如舉個例子:多個人同時看一本書沒問題,但如果有人要修改書的內(nèi)容,就必須等所有人都看完、放下書之后才能開始修改。
在 SQL 中,可以通過以下方式顯式加共享鎖:
執(zhí)行 SELECT ... LOCK IN SHARE MODE 語句時,會對讀取的行加共享鎖。但這種用法比較少見,通常用在需要確保讀取后到事務(wù)提交期間數(shù)據(jù)不被修改的場景。
3.2 排他鎖(Exclusive Lock,X 鎖)
排他鎖是獨占式的鎖。當一個事務(wù)對某行數(shù)據(jù)加上排他鎖后,其他事務(wù)既不能加共享鎖(不能讀),也不能加排他鎖(不能寫)。只有持有排他鎖的事務(wù)可以讀寫該行數(shù)據(jù)。
再用上面書的例子,比如當有人要修改書的內(nèi)容時,他必須獨占這本書,其他人既不能看也不能改。
執(zhí)行 UPDATE、DELETE、INSERT 語句時,MySQL 會自動對涉及的行加排他鎖。也可以通過 SELECT ... FOR UPDATE顯式加排他鎖。
3.3 鎖的兼容性矩陣
理解共享鎖和排他鎖的關(guān)鍵是掌握它們的兼容性:
- S 鎖 + S 鎖:兼容。多個事務(wù)可以同時持有同一行的共享鎖。
- S 鎖 + X 鎖:不兼容。如果一行已經(jīng)被加了共享鎖,其他事務(wù)無法加排他鎖;反之亦然。
- X 鎖 + X 鎖:不兼容。一行只能被一個事務(wù)加排他鎖。
這個矩陣可以總結(jié)為:讀讀可以并行,讀寫互斥,寫寫互斥。
四、意向鎖:表級鎖與行級鎖的協(xié)調(diào)者
要理解意向鎖,我們先看一個問題:
假設(shè)事務(wù) A 對某個表的第 1000 行加了行級排他鎖。此時事務(wù) B 想對整張表加表級排他鎖,MySQL 如何判斷能否加鎖成功?
最直觀的方法是:遍歷表的所有行,檢查是否有行級鎖。但這個開銷太大了,如果表有幾百萬行,每次加表級鎖都要掃描一遍,性能會急劇下降。
意向鎖就是為了解決這個問題而設(shè)計的。它是一種表級鎖,分為兩種:
4.1 意向共享鎖(IS 鎖)
當一個事務(wù)想要對表中的某些行加共享鎖時,必須先對該表加意向共享鎖。意向共享鎖表達的意思是:"我打算對這張表的某些行加共享鎖"。
4.2 意向排他鎖(IX 鎖)
當一個事務(wù)想要對表中的某些行加排他鎖時,必須先對該表加意向排他鎖。意向排他鎖表達的意思是:"我打算對這張表的某些行加排他鎖"。
4.3 意向鎖的作用機制
有了意向鎖,前面的問題就簡單了:
- 事務(wù) A 對第 1000 行加排他鎖時,會先對該表加意向排他鎖(IX 鎖)
- 事務(wù) B 想對整張表加表級排他鎖時,只需檢查該表是否有意向鎖
- 發(fā)現(xiàn)有 IX 鎖,說明表中某些行已被加鎖,無法加表級排他鎖,事務(wù) B 等待
這樣就避免了逐行掃描的開銷。意向鎖之間是兼容的,因為它們只是"意向",真正的沖突在行級鎖層面解決。
意向鎖的兼容性:
- IS 鎖與 IS 鎖、IX 鎖都兼容
- IX 鎖與 IS 鎖、IX 鎖都兼容
- 表級 S 鎖與 IS 鎖兼容,但與 IX 鎖不兼容
- 表級 X 鎖與任何意向鎖都不兼容
意向鎖對開發(fā)者是透明的,由 MySQL 自動管理,我們通常不需要顯式操作。但理解它的機制有助于我們更好地理解 InnoDB 的鎖體系。
五、記錄鎖、間隙鎖與臨鍵鎖:解決幻讀的利器
行級鎖聽起來很簡單,但 InnoDB 的行級鎖實際上包含多種細分類型。這些鎖的設(shè)計主要是為了在不同隔離級別下解決不同的并發(fā)問題,特別是幻讀問題。
5.1 記錄鎖(Record Lock)
記錄鎖是最基礎(chǔ)的行級鎖,它鎖定的是索引記錄本身。注意,這里強調(diào)"索引記錄",因為 InnoDB 的鎖是加在索引上的,而不是直接加在數(shù)據(jù)行上。
如果查詢條件沒有使用索引,InnoDB 會使用隱藏的主鍵索引(聚簇索引)來加鎖。如果是通過二級索引查詢,會先鎖定二級索引記錄,然后再鎖定對應(yīng)的主鍵索引記錄。
舉個例子:表中有一條記錄 id=10,當執(zhí)行 UPDATE users SET name='張三' WHERE id=10 時,會對 id=10 這條索引記錄加記錄鎖。
5.2 間隙鎖(Gap Lock)
間隙鎖是 InnoDB 在可重復(fù)讀隔離級別下為了防止幻讀而引入的鎖機制。它鎖定的不是記錄本身,而是索引記錄之間的"間隙"。
假設(shè)表中有三條記錄,id 分別是 10、20、30。那么存在以下間隙:
- 負無窮到 10 之前 (-∞,10)
- 10 到 20 之間 (10,20)
- 20 到 30 之間 (20,30)
- 30 到正無窮之后 (30,+∞)
當執(zhí)行 SELECT * FROM users WHERE id > 10 AND id < 20 FOR UPDATE 時,即使范圍內(nèi)沒有記錄,InnoDB 也會對這個間隙加鎖,防止其他事務(wù)在這個范圍內(nèi)插入新記錄。
間隙鎖的特點:
- 只在可重復(fù)讀隔離級別下生效。在讀已提交隔離級別下,間隙鎖會被禁用,只使用記錄鎖。
- 間隙鎖之間不沖突。多個事務(wù)可以同時持有同一個間隙的間隙鎖,因為間隙鎖的唯一目的是防止插入,而不是防止讀寫已有記錄。
- 間隙鎖會降低并發(fā)性能。它鎖定的范圍可能很大,會阻塞其他事務(wù)的插入操作。
5.3 臨鍵鎖(Next-Key Lock)
臨鍵鎖是記錄鎖和間隙鎖的組合,它鎖定的是一個左開右閉的區(qū)間。這是 InnoDB 在可重復(fù)讀隔離級別下的默認行級鎖。
還是剛才的例子,表中有 id 為 10、20、30 的記錄。當執(zhí)行范圍查詢時,可能會加上以下臨鍵鎖:
- (-∞, 10]
- (10, 20]
- (20, 30]
- (30, +∞]
臨鍵鎖既鎖定記錄本身,又鎖定記錄前面的間隙,能夠同時防止幻讀和不可重復(fù)讀。
5.4 為什么需要這么復(fù)雜的鎖?
這些鎖的設(shè)計都是為了在不同場景下平衡性能和一致性:
場景一:點查詢
執(zhí)行 SELECT * FROM users WHERE id = 10 FOR UPDATE,只需要記錄鎖,鎖定 id=10 這一條記錄即可。
場景二:范圍查詢
執(zhí)行 SELECT * FROM users WHERE id >= 10 AND id <= 20 FOR UPDATE,需要使用臨鍵鎖,鎖定區(qū)間內(nèi)的所有記錄以及區(qū)間本身,防止幻讀。
場景三:不存在的記錄
執(zhí)行 SELECT * FROM users WHERE id = 15 FOR UPDATE,如果 id=15 不存在,會對(10, 20)這個間隙加間隙鎖,防止其他事務(wù)插入 id=15 的記錄。
六、鎖與事務(wù)隔離級別的關(guān)系
MySQL 的鎖機制與事務(wù)隔離級別緊密相關(guān)。不同的隔離級別使用不同的鎖策略來保證數(shù)據(jù)一致性。
6.1 讀未提交(Read Uncommitted)
這是最低的隔離級別,幾乎不使用鎖。讀操作不加鎖,可以讀到其他事務(wù)未提交的數(shù)據(jù)(臟讀)。寫操作會加排他鎖,但只鎖定記錄本身。
這個級別性能最好,但一致性最差,實際應(yīng)用中幾乎不使用。
6.2 讀已提交(Read Committed)
在這個級別下,讀操作使用快照讀(MVCC),不加鎖。寫操作會加記錄鎖,但不會使用間隙鎖。
這意味著可以避免臟讀,但無法避免不可重復(fù)讀和幻讀。比如:
- 事務(wù) A 第一次查詢 id=10 的記錄,看到 name='張三'
- 事務(wù) B 更新這條記錄為 name='李四'并提交
- 事務(wù) A 第二次查詢 id=10 的記錄,看到 name='李四'
同一個事務(wù)中兩次讀取的結(jié)果不一致,這就是不可重復(fù)讀。
6.3 可重復(fù)讀(Repeatable Read)
這是 InnoDB 的默認隔離級別。在這個級別下:
- 讀操作使用快照讀(MVCC),讀取的是事務(wù)開始時的數(shù)據(jù)快照
- 寫操作使用臨鍵鎖,既鎖定記錄本身,也鎖定間隙
通過 MVCC,同一個事務(wù)中多次讀取同一條記錄,看到的結(jié)果是一致的,解決了不可重復(fù)讀問題。通過間隙鎖,防止其他事務(wù)在范圍內(nèi)插入新記錄,解決了幻讀問題。
但需要注意,快照讀只能防止部分幻讀。如果使用當前讀(如 SELECT ... FOR UPDATE),仍可能出現(xiàn)幻讀,此時就需要依靠間隙鎖來解決。
6.4 串行化(Serializable)
這是最高的隔離級別,所有讀操作都會加共享鎖,寫操作加排他鎖,事務(wù)之間完全串行執(zhí)行。這個級別保證了最強的一致性,但性能最差,實際應(yīng)用中很少使用。
6.5 隔離級別選擇的權(quán)衡
- 讀未提交:性能好,一致性差,幾乎不用
- 讀已提交:Oracle、SQL Server 的默認級別,適合對一致性要求不高的場景
- 可重復(fù)讀:MySQL 的默認級別,平衡了性能和一致性,是最常用的選擇
- 串行化:一致性最強,性能最差,只在特殊場景使用
七、兩階段鎖協(xié)議:鎖的生命周期
InnoDB 采用兩階段鎖協(xié)議(Two-Phase Locking,2PL)來管理鎖的生命周期。
7.1 什么是兩階段鎖協(xié)議?
兩階段鎖協(xié)議將事務(wù)分為兩個階段:
加鎖階段(Growing Phase):事務(wù)可以獲取鎖,但不能釋放鎖。在這個階段,事務(wù)隨著執(zhí)行逐步獲取需要的各種鎖。
釋放鎖階段(Shrinking Phase):事務(wù)可以釋放鎖,但不能再獲取新鎖。一旦事務(wù)開始釋放任何一個鎖,就進入這個階段。
在 InnoDB 中,實現(xiàn)相對簡化:事務(wù)執(zhí)行過程中隨時可以加鎖,但所有鎖只在事務(wù)提交或回滾時才統(tǒng)一釋放。這保證了事務(wù)的隔離性。
7.2 鎖的持有時間與性能
兩階段鎖協(xié)議有一個重要的性能含義:鎖的持有時間從加鎖開始,一直到事務(wù)結(jié)束。
假設(shè)一個事務(wù)需要執(zhí)行以下操作:
- 更新用戶表中的記錄(加鎖)
- 執(zhí)行一些復(fù)雜的業(yè)務(wù)邏輯計算
- 更新訂單表中的記錄(加鎖)
- 提交事務(wù)(釋放所有鎖)
在這個過程中,第 1 步獲取的鎖會一直持有到第 4 步,即使第 2 步和第 3 步并不需要這個鎖。
這啟發(fā)我們一個重要的優(yōu)化原則:盡量縮短事務(wù)的執(zhí)行時間,減少鎖的持有時間,避免長事務(wù)。具體做法包括:
- 不要在事務(wù)中執(zhí)行復(fù)雜的業(yè)務(wù)邏輯計算
- 不要在事務(wù)中進行遠程調(diào)用
- 將不需要事務(wù)保護的操作移到事務(wù)外面
八、死鎖
8.1 什么是死鎖?
死鎖是指兩個或多個事務(wù)互相等待對方釋放鎖,導(dǎo)致所有事務(wù)都無法繼續(xù)執(zhí)行的狀態(tài)。
經(jīng)典的死鎖場景:
- 事務(wù) A 鎖定了資源 1,準備鎖定資源 2
- 事務(wù) B 鎖定了資源 2,準備鎖定資源 1
- 事務(wù) A 等待事務(wù) B 釋放資源 2,事務(wù) B 等待事務(wù) A 釋放資源 1
- 雙方都在等待,誰也無法繼續(xù)
8.2 死鎖的四個必要條件
死鎖的發(fā)生需要同時滿足四個條件:
互斥條件:資源只能被一個事務(wù)占用,其他事務(wù)必須等待。這是鎖的本質(zhì)特性,無法消除。
持有并等待:事務(wù)持有至少一個資源,并等待獲取其他資源。這也是兩階段鎖協(xié)議的必然結(jié)果。
不可剝奪:資源不能被強行奪走,只能由持有者主動釋放。MySQL 的鎖機制也具有這個特性。
循環(huán)等待:存在一個資源等待環(huán)路,每個事務(wù)都在等待環(huán)路中下一個事務(wù)持有的資源。這是死鎖的關(guān)鍵特征。
只有同時滿足這四個條件,才會發(fā)生死鎖。破壞任何一個條件,就能避免死鎖。
8.3 如何避免死鎖?
雖然 MySQL 可以自動檢測和處理死鎖,但頻繁的死鎖會嚴重影響性能。我們應(yīng)該從設(shè)計層面就盡量避免死鎖:
按相同順序訪問資源:如果所有事務(wù)都按照相同的順序獲取鎖,就不會形成循環(huán)等待。比如,總是先鎖用戶表,再鎖訂單表,而不是有時先鎖訂單表。
縮短事務(wù)持續(xù)時間:事務(wù)執(zhí)行時間越短,持有鎖的時間就越短,發(fā)生死鎖的概率就越低。
降低事務(wù)隔離級別:如果業(yè)務(wù)允許,可以使用讀已提交隔離級別,避免使用間隙鎖,減少鎖沖突。
合理設(shè)計索引:確保查詢都能走索引,避免全表掃描導(dǎo)致大量加鎖。
大事務(wù)拆分為小事務(wù):不要在一個事務(wù)中處理過多數(shù)據(jù),可以分批處理,減少鎖的持有時間。
圖片
九、悲觀鎖與樂觀鎖思想
除了 MySQL 內(nèi)置的鎖機制,還有兩種重要的并發(fā)控制思想:悲觀鎖和樂觀鎖。這兩種思想代表了對待并發(fā)沖突的不同態(tài)度。
9.1 悲觀鎖:先鎖后用
悲觀鎖的思想是:假設(shè)并發(fā)沖突經(jīng)常發(fā)生,所以每次操作數(shù)據(jù)時都先加鎖,確保只有自己能訪問,操作完成后再釋放鎖。
MySQL 的鎖機制本質(zhì)上就是悲觀鎖的實現(xiàn)。當執(zhí)行 SELECT ... FOR UPDATE 時,就是在使用悲觀鎖:先把數(shù)據(jù)鎖住,確保別人不能修改,然后再進行操作。
悲觀鎖的特點:
- 實現(xiàn)簡單,由數(shù)據(jù)庫保證并發(fā)安全
- 性能開銷較大,會阻塞其他事務(wù)
- 適合并發(fā)沖突頻繁的場景
- 可能導(dǎo)致死鎖
9.2 樂觀鎖:先用后驗
樂觀鎖的思想是:假設(shè)并發(fā)沖突很少發(fā)生,所以不加鎖,直接操作數(shù)據(jù)。但在提交時檢查是否有沖突,如果有沖突就放棄操作并重試。
樂觀鎖通常通過版本號或時間戳來實現(xiàn)。每條記錄包含一個版本號字段,每次更新時:
- 讀取記錄及其版本號,比如版本號為 5
- 在本地完成業(yè)務(wù)邏輯計算
- 提交更新時,檢查版本號是否仍為 5
- 如果仍為 5,說明沒有沖突,更新數(shù)據(jù)并將版本號改為 6
- 如果不是 5,說明有其他事務(wù)已經(jīng)修改過,本次更新失敗,需要重試
樂觀鎖的特點:
- 不需要數(shù)據(jù)庫鎖,不會阻塞其他事務(wù)
- 性能好,適合讀多寫少的場景
- 實現(xiàn)相對復(fù)雜,需要應(yīng)用層處理沖突
- 需要額外的版本號字段
9.3 如何選擇?
選擇悲觀鎖還是樂觀鎖,取決于具體的業(yè)務(wù)場景:
使用悲觀鎖的場景:
- 寫操作頻繁,沖突概率高
- 每次沖突的代價很高(比如涉及復(fù)雜計算)
- 需要強一致性保證
- 典型場景:秒殺、庫存扣減、賬戶余額更新
使用樂觀鎖的場景:
- 讀操作遠多于寫操作
- 沖突概率低
- 可以容忍偶爾的重試
- 典型場景:用戶信息修改、文章編輯、商品詳情更新
實際項目中,往往是兩者結(jié)合使用。對于核心的高沖突資源使用悲觀鎖,對于邊緣的低沖突資源使用樂觀鎖。
十、鎖優(yōu)化
最后我們來聊聊實際開發(fā)中如何優(yōu)化鎖的使用,避免鎖相關(guān)的性能問題。
10.1 索引優(yōu)化:減少鎖的范圍
InnoDB 的鎖是加在索引上的,如果沒有合適的索引,查詢會走全表掃描,導(dǎo)致鎖定大量無關(guān)的記錄,甚至退化成表級鎖。
反例:沒有索引的更新
UPDATE users SET status = 1 WHERE name = '張三';如果 name 字段沒有索引,這條 SQL 會掃描全表,鎖定所有記錄。
正例:有索引的更新
-- 在name字段上建立索引
CREATE INDEX idx_name ON users(name);
UPDATE users SET status = 1 WHERE name = '張三';此時只會鎖定 name='張三'的記錄。
10.2 盡量使用主鍵或唯一索引
使用主鍵或唯一索引進行查詢,可以精確定位記錄,避免加不必要的間隙鎖。
如果 WHERE 條件是非唯一索引,可能會鎖定多條記錄以及它們之間的間隙。如果 WHERE 條件是主鍵或唯一索引,通常只鎖定一條記錄,不會加間隙鎖。
10.3 減小事務(wù)范圍
事務(wù)越大,持有鎖的時間越長,發(fā)生鎖沖突的概率越高。應(yīng)該:
- 只在真正需要事務(wù)保護的地方使用事務(wù)
- 不要在事務(wù)中執(zhí)行耗時的操作(如遠程調(diào)用、復(fù)雜計算)
- 將大事務(wù)拆分為多個小事務(wù)
10.4 避免在循環(huán)中逐條操作
反例:在循環(huán)中逐條更新
-- 偽代碼
for each userId in userList:
UPDATE users SET score = score + 1 WHERE id = userId;每次更新都是一個單獨的事務(wù),效率低,而且容易導(dǎo)致死鎖。
正例:批量更新
UPDATE users SET score = score + 1 WHERE id IN (1, 2, 3, 4, 5);一次更新多條記錄,減少事務(wù)數(shù)量和鎖等待時間。
10.5 合理設(shè)置事務(wù)隔離級別
不是所有場景都需要可重復(fù)讀隔離級別。如果業(yè)務(wù)可以容忍不可重復(fù)讀,可以使用讀已提交隔離級別,避免間隙鎖,提高并發(fā)性能。
特別是在高并發(fā)的互聯(lián)網(wǎng)應(yīng)用中,很多公司都選擇讀已提交作為默認隔離級別。
10.6 監(jiān)控和診斷鎖問題
當遇到性能問題時,可以使用以下方法診斷鎖相關(guān)問題:
查看當前鎖等待情況:
SELECT * FROM information_schema.innodb_locks;
SELECT * FROM information_schema.innodb_lock_waits;查看最近的死鎖信息:
SHOW ENGINE INNODB STATUS;這個命令會顯示最近一次死鎖的詳細信息,包括涉及的事務(wù)、持有的鎖、等待的鎖等,對于分析死鎖原因非常有幫助。


































