目錄
1.了解過 MySQL 死鎖問題嗎?
2.什么是線程死鎖?死鎖相關面試題
2.1 什么是死鎖:
2.2 形成死鎖的四個必要條件是什么?
2.3 如何避免線程死鎖?
3. MySQL 怎么排查死鎖問題?
4.Java線上死鎖問題如何排查
5. 詳細說一下 MySQL 數據庫中鎖的分類(重要)
6.MySQL行級鎖的原理是什么
7. 行鎖什么時候會退化成表鎖
1.了解過 MySQL 死鎖問題嗎?
分析:
解釋 MySQL 死鎖是如何發生的。
- 回答:了解過。
- 在并發事務中、當兩個事務出現循環資源依賴、這兩個事務都在等待別的事務釋放資源時、就會導致這兩個事務都進入無限等待的狀態、這時候就發生了死鎖
2.什么是線程死鎖?死鎖相關面試題
2.1 什么是死鎖:
- 死鎖是指兩個或兩個以上的進程(線程)在執行過程中、由于競爭資源而造成的一種阻塞的現象、若無外力作用、它們都將無法推進下去。此時稱系統處于死鎖狀態或系統產生了死鎖、這些永遠在互相等待的進程(線程)稱為死鎖進程(線程)。
- 多個線程同時被阻塞、它們中的一個或者全部都在等待某個資源被釋放。由于線程被無限期地阻塞、因此程序不可能正常終止。
2.2 形成死鎖的四個必要條件是什么?
- 互斥條件:在一段時間內某資源只由一個進程占用。如果此時還有其它進程請求資源、就只能等待、直至占有資源的進程用畢釋放。
- 占有且等待條件:指進程已經保持至少一個資源、但又提出了新的資源請求、而該資源已被其它進程占有、此時請求進程阻塞、但又對自己已獲得的其它資源保持不放。
- 不可搶占條件:別人已經占有了某項資源、你不能因為自己也需要該資源、就去把別人的資源搶過來。
- 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。
- (比如一個進程集合、A在等B、B在等C、C在等A)
總結:
產生死鎖的四個必要條件:
- 互斥條件:多個線程不能同時使用一個資源
- 持有并等待條件:線程A在等待資源2的同時并不會釋放自己已經持有的資源1
- 不可剝奪條件:在自己使用之前不能被其他線程獲取
- 循環等待條件:兩個線程獲取資源的順序構成了環形鏈
2.3 如何避免線程死鎖?
我們只要破壞產生死鎖的四個條件中的其中一個就可以了。
- 破壞互斥條件:這個條件我們沒有辦法破壞、因為我們用鎖本來就是想讓他們互斥的(臨界資源需要互斥訪問)。
- 破壞請求與保持條件:一次性申請所有的資源
- 破壞不剝奪條件:占用部分資源的線程進一步申請其他資源時、如果申請不到、可以主動釋放它占有的資源。
- 破壞循環等待條件:靠按序申請資源來預防、按某一順序申請資源、釋放資源則反序釋放。破壞循環等待條件。
3. MySQL 怎么排查死鎖問題?
分析:獲取死鎖日志、分析死鎖日志
參考面試回答:
- 在遇到線上死鎖問題時、我們應該第一時間獲取相關的死鎖日志。
- 我們可以通過
show engine innodb status
命令來獲取死鎖信息。 - 然后就分析死鎖日志。死鎖日志通常分為兩部分、上半部分說明了事務1在等待什么鎖、下半部分說明了事務2當前持有的鎖和等待的鎖。
- 通過閱讀死鎖日志、我們可以清楚地知道兩個事務形成了怎樣的循環等待、然后根據當前各個事務執行的SQL分析出加鎖類型以及順序、逆向推斷出如何形成循環等待、這樣就能找到死鎖產生的原因了
4.Java線上死鎖問題如何排查
發生死鎖的場景
循環等待 (Circular Wait): 這是最經典的死鎖場景。也就是嵌套鎖
-
線程 A 持有鎖 1、并嘗試獲取鎖 2。
-
線程 B 持有鎖 2、并嘗試獲取鎖 1。
-
結果:線程 A 和線程 B 都在等待對方釋放鎖、導致永久阻塞。
競爭不可剝奪資源:
兩個進程都需要打印機和掃描儀。進程A先獲得了打印機\進程B先獲得了掃描儀。然后進程A請求掃描儀
進程B請求打印機 由于打印機和掃描儀都是不可剝奪的 兩個進程都無法獲得對方需要的資源 導致死鎖。
可以簡單概括如下:
-
識別死鎖發生的現象: 確定應用是否表現出死鎖的癥狀、如線程長時間處于阻塞狀態。
-
獲取線程堆棧信息: 通過工具(如
jstack
)獲取JVM線程堆棧、分析各線程的狀態、尤其關注等待鎖的線程。jstack
+threaddump
.txt、收集線程堆棧信息、threaddump
.txt文件。 -
分析代碼: 檢查線程堆棧中的棧幀,定位發生死鎖的代碼區域。重點關注可能導致鎖定的同步塊或方法。在上一步生成的堆棧文件中查找
deadlock
、waiting to lock
、lock
等有指向死鎖的信息、確定代碼中出現死鎖的位置。 -
優化代碼邏輯: 修復導致死鎖的代碼塊,一般可以采用減少鎖的粒度,使用非阻塞算法,或者重構為無鎖設計。使用
ReentrantLock
和tryLock()
等機制避免長期持有鎖等方式。 -
監控和測試: 持續監控應用運行時的線程情況,尤其是在高并發場景下。通過壓力測試和代碼審計盡早發現潛在的死鎖問題。
面試回答:
- 首先就是命令
jps
查看進程ID - 然后將進程ID對應的程序線程日志收集到文本中 方便后續分析
jstack -l 24360 > .wy.txt
- 然后分析進程堆棧信息
- 打開 文件、搜索
deadlock
、lock waiting to
、locked
關鍵字、以定位死鎖或阻塞線程 - 然后優化代碼邏輯
5. 詳細說一下 MySQL 數據庫中鎖的分類(重要)
分析:
-
全局鎖:通過
flush tables with read lock
語句會將整個數據庫就處于只讀狀態了、這時其他線程執行以下操作、增刪改或者表結構修改都會阻塞。全局鎖主要應用于做全庫邏輯備份、這樣在備份數據庫期間,不會因為數據或表結構的更新、而出現備份文件的數據與預期的不一樣。 -
表級鎖:MySQL 里面表級別的鎖有這幾種:
-
表鎖:通過
lock tables
語句可以對表加表鎖、表鎖除了會限制別的線程的讀寫外、也會限制本線程接下來的讀寫操作。 -
元數據鎖:當我們對數據庫表進行操作時、會自動給這個表加上 MDL、對一張表進行 CRUD 操作時、加的是 MDL 讀鎖、對一張表做結構變更操作的時候、加的是 MDL 寫鎖、MDL 是為了保證當用戶對表執行 CRUD 操作時、防止其他線程對這個表結構做了變更。
-
意向鎖:當執行插入、更新、刪除操作、需要先對表加上「意向獨占鎖」、然后對該記錄加獨占鎖。意向鎖的目的是為了快速判斷表里是否有記錄被加鎖。
-
-
行級鎖:InnoDB 引擎是支持行級鎖的、而 MyISAM 引擎并不支持行級鎖。
-
記錄鎖:鎖住的是一條記錄。而且記錄鎖是有 S 鎖和 X 鎖之分的、滿足讀寫互斥、寫寫互斥
-
間隙鎖:只存在于可重復讀隔離級別、目的是為了解決可重復讀隔離級別下幻讀的現象。
-
Next-Key Lock 稱為臨鍵鎖、是 Record Lock + Gap Lock 的組合、鎖定一個范圍、并且鎖定記錄本身。
-
插入意向鎖、當插入位置的下一條記錄有間隙鎖、那么就會生成插入意向鎖、然后進入阻塞狀態
-
根據鎖粒度的不同、MySQL 的鎖可以分為全局鎖、表級鎖、行級鎖。
-
我們熟悉的是表級鎖和行級鎖
-
比如我們對一張表結構進行修改的時候
-
MySQL 就會對這張表加一個元數據鎖、元數據鎖是屬于表級鎖的。
-
-
行級鎖目前只有 InnoDB 存儲引擎實現了、MyISAM 存儲引擎是不支持行級鎖的、只有表鎖。
-
InnoDB 存儲引擎實現的行級鎖主要有記錄鎖、間隙鎖、臨鍵鎖、插入意向鎖這些
-
-
當我們對表記錄進行
select for update
、或者增刪改的時候、都會對記錄加行級鎖。 -
mysql的鎖機制是數據庫引擎鎖的鎖機制
-
間隙鎖只在某些情況下才加:為了防止幻讀
-
當前事務隔離級別是可重復隔離級別(MySQL默認)
-
查詢使用了 FOR UPDATE 或 LOCK IN SHARE MODE
-
查詢條件涉及范圍查詢 或 非唯一索引
-
在數據庫使用非唯一索引時、系統會使用間隙鎖來防止其他事務在當前范圍內插入新記錄、確保當前事務的操作是可重復讀的。這是為了防止幻讀現象(Phantom Read)、即一個事務在讀取數據時、另一個事務可能插入了新的數據、使得前一個事務讀取的數據不一致。
-
間隙鎖會鎖住一個范圍的空白地帶、這樣即使在該范圍內沒有記錄、其他事務也不能插入新的記錄、從而避免了出現不一致的數據讀取。
-
6.MySQL行級鎖的原理是什么
??行級鎖是 MySQL 在存儲引擎層(如 InnoDB)實現的、鎖定的是表中某一行數據
??首先行鎖分為:
-
記錄鎖:鎖住單個記錄
-
間隙鎖:這種鎖鎖定的是索引記錄之間的“間隙”,但不包括記錄本身。例如如果索引上有值 10 和 20、間隙鎖可以鎖定 (10, 20) 這個開區間。它的主要目的是防止其他事務在這個間隙中插入新的記錄、從而避免幻讀問題。間隙鎖只在可重復讀(Repeatable Read)或更高的隔離級別下才生效。
-
Next-Key Lock 稱為臨鍵鎖、是 Record Lock + Gap Lock 的組合、鎖定一個范圍、并且鎖定記錄本身。用于防止幻讀、默認用于可重復度隔離級別。Next-Key Lock 是 InnoDB 在可重復讀隔離級別下解決幻讀問題的主要方式、是其默認的行鎖算法
實現原理:
1. 實現原理核心 —— 依賴索引:
InnoDB 的行級鎖是通過給索引項加鎖來實現的
。這意味著當 InnoDB 更新、刪除或(在某些情況下)查詢一行數據、它實際上是在這條記錄對應的索引條目上施加鎖。
一句話總結就是:
如果 SQL 語句的操作能夠利用到索引(尤其是唯一索引或主鍵索引)、InnoDB 就會使用行級鎖。
2. 行鎖什么時候會退化成表鎖
如果 SQL 語句的條件沒有命中任何索引、導致需要進行全表掃描、那么 InnoDB 通常會退化為對整個表加鎖(表級鎖)、或者鎖定所有掃描過的行、這會極大地降低并發性能。
行鎖分為:
- 記錄鎖:鎖住單個記錄
- 間隙鎖:這種鎖鎖定的是索引記錄之間的“間隙”,但不包括記錄本身。例如如果索引上有值 10 和 20、間隙鎖可以鎖定 (10, 20) 這個開區間。它的主要目的是防止其他事務在這個間隙中插入新的記錄、從而避免幻讀問題。間隙鎖只在可重復讀(Repeatable Read)或更高的隔離級別下才生效。
- Next-Key Lock 稱為臨鍵鎖、是 Record Lock + Gap Lock 的組合、鎖定一個范圍、并且鎖定記錄本身。用于防止幻讀、默認用于可重復度隔離級別。Next-Key Lock 是 InnoDB 在可重復讀隔離級別下解決幻讀問題的主要方式、是其默認的行鎖算法
行鎖的模式 (并發控制):
- 當一個事務獲取了某行的鎖后、這個鎖會有不同的模式、決定了其他事務能否以及如何訪問這行數據:
- 共享鎖 (S Lock): 允許多個事務同時讀取同一行數據。一個事務獲取了某行的S鎖后、其他事務也可以獲取該行的S鎖來讀取、但任何事務都不能獲取該行的排它鎖來進行修改、直到所有S鎖被釋放。通常通過
SELECT ... LOCK IN SHARE MODE
獲取。 - 排它鎖 (X Lock): 如果一個事務獲取了某行的X鎖、那么其他任何事務都不能再獲取該行的S鎖或X鎖、直到第一個事務釋放X鎖。這意味著獲取X鎖的事務可以獨占地讀取和修改這行數據。通常通過
SELECT ... FOR UPDATE
、或者在INSERT
、UPDATE
、DELETE
操作中隱式獲取。
加鎖方式是:
-
執行如 SELECT ... FOR UPDATE、UPDATE、DELETE 語句時、InnoDB 會為匹配行加排它鎖
-
SELECT ... LOCK IN SHARE MODE 則會加共享鎖
7. 行鎖什么時候會退化成表鎖
一句話面試回答:
-
如果 SQL 語句的條件沒有命中任何索引、導致需要進行全表掃描
-
那么 InnoDB 通常會退化為對整個表加鎖(表級鎖)、或者鎖定所有掃描過的、,這會極大地降低并發性能
分析:
-
InnoDB 行鎖的基礎:鎖定索引記錄。我們首先要記住、InnoDB 實現行級鎖的機制是在索引記錄上加鎖。當我們說鎖住某一行時、InnoDB 實際上是在這一行數據對應的索引條目上施加了鎖。
-
無索引時的全表掃描
-
如果一個
UPDATE
或DELETE
語句的WHERE
子句中的列沒有建立索引、或者優化器因為某種原因(比如索引選擇性不高、數據量小等)決定不使用索引、那么 InnoDB 為了找到符合條件的行、就必須逐行掃描整個表的數據。
-
-
全表掃描時的加鎖行為
-
InnoDB 的行為通常是:它會在掃描過程中、對它訪問到的每一行數據都嘗試加上行級鎖(通常是排他鎖 X Lock)。
-
即使某一行最終不符合
WHERE
條件、在它被掃描和判斷的過程中、也可能被短暫地加鎖。對于最終符合條件的行、鎖會一直持有直到事務提交或回滾。
-
-
退化成表鎖的實際效果
-
當 InnoDB 對全表掃描過程中接觸到的幾乎所有行都加上了行級鎖時、盡管從機制上講仍然是多個行鎖,但其最終效果就非常類似于對整個表加了一個表鎖。
-
這是因為:如果一個事務鎖住了表中的大部分或所有、,其他需要訪問這些行(即使是不同的行)的事務都會被阻塞,等待這些行鎖被釋放。此時并發性會急劇下降、就好像整個表被鎖住了一樣。
-
-
為什么不直接用一個表鎖
-
InnoDB 的設計是盡可能提供細粒度的并發控制。即使是全表掃描、它也是在行級別上進行判斷和加鎖、這在某些特定情況下
-
(例如如果掃描的表非常小、或者符合條件的行很快被找到并鎖定而、其他行的鎖能快速釋放)可能仍然比一個粗暴的表鎖要好一點點、或者至少在內部機制上保持一致性。但對于用戶感知到的并發性能而言、效果往往等同于表鎖
-