1、背景
對於數據庫系統來講在多用戶併發條件下提升併發性的同時又要保證數據的一致性一直是數據庫系統追求的目標,既要知足大量併發訪問的需求又必須保證在此條件下數據的安全,爲了知足這一目標大多數數據庫經過鎖和事務機制來實現,MySQL數據庫也不例外。儘管如此咱們仍然會在業務開發過程當中遇到各類各樣的疑難問題,本文將以案例的方式演示常見的併發問題並分析解決思路。html
2、表鎖致使的慢查詢的問題mysql
首先咱們看一個簡單案例,根據ID查詢一條用戶信息:算法
mysql> select * from user where id=6;
這個表的記錄總數爲3條,但卻執行了13秒。sql
出現這種問題咱們首先想到的是看看當前MySQL進程狀態:
數據庫
從進程上能夠看出select語句是在等待一個表鎖,那麼這個表鎖又是什麼查詢產生的呢?這個結果中並無顯示直接的關聯關係,但咱們能夠推測多半是那條update語句產生的(由於進程中沒有其餘可疑的SQL),爲了印證咱們的猜想,先檢查一下user表結構:
安全
果真user表使用了MyISAM存儲引擎,MyISAM在執行操做前會產生表鎖,操做完成再自動解鎖。若是操做是寫操做,則表鎖類型爲寫鎖,若是操做是讀操做則表鎖類型爲讀鎖。正如和你理解的同樣寫鎖將阻塞其餘操做(包括讀和寫),這使得全部操做變爲串行;而讀鎖狀況下讀-讀操做能夠並行,但讀-寫操做仍然是串行。如下示例演示了顯式指定了表鎖(讀鎖),讀-讀並行,讀-寫串行的狀況。session
顯式開啓/關閉表鎖,使用lock table user read/write; unlock tables;
session1:
session2:
併發
能夠看到會話1啓用表鎖(讀鎖)執行讀操做,這時會話2能夠並行執行讀操做,但寫操做被阻塞。接着看:
session1:
session2:
高併發
當session1執行解鎖後,seesion2則馬上開始執行寫操做,即讀-寫串行。工具
總結:
到此咱們把問題的緣由基本分析清楚,總結一下——MyISAM存儲引擎執行操做時會產生表鎖,將影響其餘用戶對該表的操做,若是表鎖是寫鎖,則會致使其餘用戶操做串行,若是是讀鎖則其餘用戶的讀操做能夠並行。因此有時咱們遇到某個簡單的查詢花了很長時間,看看是否是這種狀況。
解決辦法:
一、儘可能不用MyISAM存儲引擎,在MySQL8.0版本中已經去掉了全部的MyISAM存儲引擎的表,推薦使用InnoDB存儲引擎。
二、若是必定要用MyISAM存儲引擎,減小寫操做的時間;
3、線上修改表結構有哪些風險?
若是有一天業務系統須要增大一個字段長度,可否在線上直接修改呢?在回答這個問題前,咱們先來看一個案例:
以上語句嘗試修改user表的name字段長度,語句被阻塞。按照慣例,咱們檢查一下當前進程:
從進程能夠看出alter語句在等待一個元數據鎖,而這個元數據鎖極可能是上面這條select語句引發的,事實正是如此。在執行DML(select、update、delete、insert)操做時,會對錶增長一個元數據鎖,這個元數據鎖是爲了保證在查詢期間表結構不會被修改,所以上面的alter語句會被阻塞。那麼若是執行順序相反,先執行alter語句,再執行DML語句呢?DML語句會被阻塞嗎?例如我正在線上環境修改表結構,線上的DML語句會被阻塞嗎?答案是:不肯定。
在MySQL5.6開始提供了online ddl功能,容許一些DDL語句和DML語句併發,在當前5.7版本對online ddl又有了加強,這使得大部分DDL操做能夠在線進行。詳見:https://dev.mysql.com/doc/ref...
因此對於特定場景執行DDL過程當中,DML是否會被阻塞須要視場景而定。
總結:
經過這個例子咱們對元數據鎖和online ddl有了一個基本的認識,若是咱們在業務開發過程當中有在線修改表結構的需求,能夠參考如下方案:
一、儘可能在業務量小的時間段進行;
二、查看官方文檔,確認要作的表修改能夠和DML併發,不會阻塞線上業務;
三、推薦使用percona公司的pt-online-schema-change工具,該工具被官方的online ddl更爲強大,它的基本原理是:經過insert… select…語句進行一次全量拷貝,經過觸發器記錄表結構變動過程當中產生的增量,從而達到表結構變動的目的。
例如要對A表進行變動,主要步驟爲:
一、建立目的表結構的空表,A_new;
二、在A表上建立觸發器,包括增、刪、改觸發器;
三、經過insert…select…limit N 語句分片拷貝數據到目的表
四、Copy完成後,將A_new表rename到A表。
4、一個死鎖問題的分析
在線上環境下死鎖的問題偶有發生,死鎖是由於兩個或多個事務相互等待對方釋放鎖,致使事務永遠沒法終止的狀況。爲了分析問題,咱們下面將模擬一個簡單死鎖的狀況,而後從中總結出一些分析思路。
演示環境:MySQL5.7.20 事務隔離級別:RR
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(300) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
下面演示事務一、事務2工做的狀況:
InnoDB狀態有不少指標,這裏咱們截取死鎖相關的信息,能夠看出InnoDB能夠輸出最近出現的死鎖信息,其實不少死鎖監控工具也是基於此功能開發的。
在死鎖信息中,顯示了兩個事務等待鎖的相關信息(藍色表明事務一、綠色表明事務2),重點關注:WAITING FOR THIS LOCK TO BE GRANTED和HOLDS THE LOCK(S)。
WAITING FOR THIS LOCK TO BE GRANTED表示當前事務正在等待的鎖信息,從輸出結果看出事務1正在等待heap no爲5的行鎖,事務2正在等待 heap no爲7的行鎖;
HOLDS THE LOCK(S):表示當前事務持有的鎖信息,從輸出結果看出事務2持有heap no爲5行鎖。
從輸出結果看出,最後InnoDB回滾了事務2。
那麼InnoDB是如何檢查出死鎖的呢?
咱們想到最簡單方法是假如一個事務正在等待一個鎖,若是等待時間超過了設定的閾值,那麼該事務操做失敗,這就避免了多個事務彼此長等待的狀況。參數innodb_lock_wait_timeout正是用來設置這個鎖等待時間的。
若是按照這個方法,解決死鎖是須要時間的(即等待超過innodb_lock_wait_timeout設定的閾值),這種方法稍顯被動並且影響系統性能,InnoDB存儲引擎提供一個更好的算法來解決死鎖問題,wait-for graph算法。簡單的說,當出現多個事務開始彼此等待時,啓用wait-for graph算法,該算法斷定爲死鎖後當即回滾其中一個事務,死鎖被解除。該方法的好處是:檢查更爲主動,等待時間短。
下面是wait-for graph算法的基本原理:
爲了便於理解,咱們把死鎖看作4輛車彼此阻塞的場景:
4輛車看作4個事務,彼此等待對方的鎖,形成死鎖。wait-for graph算法原理是把事務做爲節點,事務之間的鎖等待關係,用有向邊表示,例如事務A等待事務B的鎖,就從節點A畫一條有向邊到節點B,這樣若是A、B、C、D構成的有向圖,造成了環,則判斷爲死鎖。這就是wait-for graph算法的基本原理。
總結:
一、若是咱們業務開發中出現死鎖如何檢查出?剛纔已經介紹了經過監控InnoDB狀態能夠得出,你能夠作一個小工具把死鎖的記錄收集起來,便於過後查看。
二、若是出現死鎖,業務系統應該如何應對?從上文咱們能夠看到當InnoDB檢查出死鎖後,對客戶端報出一個Deadlock found when trying to get lock; try restarting transaction信息,而且回滾該事務,應用端須要針對該信息,作事務重啓的工做,並保存現場日誌過後作進一步分析,避免下次死鎖的產生。
5、鎖等待問題的分析
在業務開發中死鎖的出現機率較小,但鎖等待出現的機率較大,鎖等待是由於一個事務長時間佔用鎖資源,而其餘事務一直等待前個事務釋放鎖。
從上述可知事務1長時間持有id=3的行鎖,事務2產生鎖等待,等待時間超過innodb_lock_wait_timeout後操做中斷,但事務並無回滾。若是咱們業務開發中遇到鎖等待,不只會影響性能,還會給你的業務流程提出挑戰,由於你的業務端須要對鎖等待的狀況作適應的邏輯處理,是重試操做仍是回滾事務。
在MySQL元數據表中有對事務、鎖等待的信息進行收集,例如information_schema數據庫下的INNODB_LOCKS、INNODB_TRX、INNODB_LOCK_WAITS,你能夠經過這些表觀察你的業務系統鎖等待的狀況。你也能夠用一下語句方便的查詢事務和鎖等待的關聯關係:
SELECT r.trx_id waiting_trx_id, r.trx_mysql_thread_id waiting_thread, r.trx_query wating_query, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread, b.trx_query blocking_query FROM information_schema.innodb_lock_waits w INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;
結果:
waiting_trx_id: 5132 waiting_thread: 11 wating_query: update user set name='hehe' where id=3 blocking_trx_id: 5133 blocking_thread: 10 blocking_query: NULL
總結:
一、請對你的業務系統作鎖等待的監控,這有助於你瞭解當前數據庫鎖狀況,以及爲你優化業務程序提供幫助;
二、業務系統中應該對鎖等待超時的狀況作合適的邏輯判斷。
6、小結
本文經過幾個簡單的示例介紹了咱們經常使用的幾種MySQL併發問題,並嘗試得出針對這些問題咱們排查的思路。文中涉及事務、表鎖、元數據鎖、行鎖,但引發併發問題的遠遠不止這些,例如還有事務隔離級別、GAP鎖等。真實的併發問題可能多而複雜,但排查思路和方法倒是能夠複用,在本文中咱們使用了show processlist;show engine innodb status;以及查詢元數據表的方法來排查發現問題,若是問題涉及到了複製,還須要藉助master/slave監控來協助。
長按二維碼關注咱們