5.MySQL中的鎖

什麼是鎖?MySQL 中提供了幾類鎖?
  鎖是實現數據庫併發控制的重要手段,能夠保證數據庫在多人同時操做時可以正常運行。MySQL 提供了全局鎖、行級鎖、表級鎖。其中 InnoDB 支持表級鎖和行級鎖,MyISAM 只支持表級鎖。html


 詳解鎖mysql


 全局鎖:sql

  對整個數據庫實例加鎖,MySQL提供了一個加全局讀鎖的命令:【Flush tables with read lock】-FTWRL。當須要讓整個庫處於只讀狀態,可以使用這個命令。其餘線程進行:增刪改、數據定義(建表、修改表)和更新類事務的提交語句都會被阻塞。
該鎖的典型使用場景是:全庫邏輯備份,即把整庫全部表都select出來存成文本。
  經過FTWRL將整庫只讀,可能會致使兩個問題:數據庫

  • 主庫上備份時,備份期間都不能執行更新,業務會停擺。
  • 從庫上備份時,備份期間從庫不能執行主庫同步過來的binlog,會致使主從延遲。

  備份加鎖是爲了不繫統備份獲得的庫與主庫不是同一個邏輯時間點,數據不一致。
如何處理邏輯備份時,整個數據庫不能插入的狀況?
  經過MySQL自帶的邏輯備份工具mysqldump,使用參數-single-transaction時,導數據以前會啓動一個事務(隔離級別是可重複讀),來確保拿到一致性視圖,因爲MVCC的支持,這個過程當中數據是能夠正常更新的。可是這種備份方式須要引擎能支持這個隔離級別,如MyISAM不支持事務,則備份須要用FTWRL命令。
FTWRL設置的全庫只讀與set global readonly = true(設置數據庫只讀)的區別:安全

  • 在有些系統,readonly的值會被用來作其餘邏輯,如用來判斷一個庫是主庫仍是備庫。所以修改global變量的方式影響更大。
  • 異常處理機制上存在差別,若是用FTWRL命令後客戶端發生異常斷開,那麼MySQL會自動釋放全局鎖,整庫回到能夠正常更新的狀態。而設置readonly後,若是客戶端異常斷開,數據庫將一直保存readonly狀態,這樣會致使整庫長時間處於不可寫狀態,風險較高。

表級鎖:
  MySQL裏表級鎖有兩種:表鎖和元數據鎖(meta data lock,MDL)
表鎖:
  語法:lock tables ...read/wirte。可用unlock tables主動釋放鎖,客戶端斷開時也會主動釋放。
  注意:lock tables會限制別的線程讀寫,也會限制本線程接下來的操做對象。若是A線程執行lock table t1 read,t2 write。則其餘線程寫t1,讀t2都會被阻塞,同時,線程A在釋放表鎖以前,也只能執行讀t1,讀寫t2的操做,寫t1也不行,也不能訪問其餘表。
  對於 InnoDB 這種支持行鎖的引擎,通常不使用 lock tables 命令來控制併發,畢竟鎖住整個表的影響面仍是太大。
MDL:
  MDL在訪問一個表時會被自動加上 ,其做用是保證讀寫正確性。在併發狀況下維護數據的一致性。MySQL5.5引入MDL,當對一個表作增刪改查時,加MDL讀鎖,當對錶結構進行變動時,加MDL寫鎖。
  讀鎖之間不互斥,保證多個線程同時對一張表。寫鎖之間是互斥的,用來保證變動表結構操做的安全性,所以,兩個線程同時給一個表加字段,其中一個要等到另外一個執行完才能開始執行。
  注意:事務中MDL鎖,在語句執行開始時申請,但在語句結束後不會當即釋放,而是等到整個事務提交後再釋放。在DML與DDL操做交互時,若是客戶端有查詢重試機制,就容易產生session爆滿,致使內存增高。
  例子:sessionA對錶t1進行查詢,此時表t1會加MDL讀鎖,此時sessionC發起DDL操做請求,但A的MDL讀鎖爲釋放,致使C申請MDL寫鎖被阻塞,後面若是有其餘的session發起查詢申請MDL讀鎖,都會被堵塞,當客戶端發起重試時,查詢過多。會致使大量session被阻塞,致使內存升高。session


行鎖:
  行級鎖是 MySQL 中粒度最小的一種鎖,他能大大減小數據庫操做的衝突。InnoDB 的行級鎖有共享鎖(S LOCK)和排他鎖(X LOCK)兩種。共享鎖容許事物讀一行記錄,不容許任何線程對該行記錄進行修改。排他鎖容許當前事物刪除或更新一行記錄,其餘線程不能操做該記錄。
  共享鎖:SELECT ... LOCK IN SHARE MODE,MySQL 會對查詢結果集中每行都添加共享鎖,前提是當前線程沒有對該結果集中的任何行使用排他鎖,不然申請會阻塞。
  排他鎖:select * from t where id=1 for update,其中 id 字段必須有索引,MySQL 會對查詢結果集中每行都添加排他鎖,在事物操做中,任何對記錄的更新與刪除操做會自動加上排他鎖。前提是當前沒有線程對該結果集中的任何行使用排他鎖或共享鎖,不然申請會阻塞。
針對兩階段協議該注意:
  在InnoDB事務中,行鎖是在須要的時候才加上,但並非不須要了就馬上釋放,而是要等到事務結束時才釋放。
  根據兩階段協議,當遇到事務中須要鎖多個行時,要把最可能形成衝突、最可能影響併發度的鎖儘可能日後放。架構


死鎖:
  是指兩個或兩個以上的進程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的過程稱爲死鎖。
常見的死鎖案例:併發

將投資的錢拆封幾份借給借款人,這時處理業務邏輯就要把若干個借款人一塊兒鎖住 select * from xxx where id in (xx,xx,xx) for update。 批量入庫,存在則更新,不存在則插入。 解決方法 insert into tab(xx,xx) on duplicate key update xx='xx'。

 如何查看死鎖?工具

  使用命令 show engine innodb status 查看最近的一次死鎖。InnoDB Lock Monitor 打開鎖監控,每 15s 輸出一第二天志。使用完畢後建議關閉,不然會影響數據庫性能。
對待死鎖常見的兩種策略:
  經過 innodblockwait_timeout 來設置超時時間,一直等待直到超時;
  發起死鎖檢測,發現死鎖以後,主動回滾死鎖中的某一個事務,讓其它事務繼續執行;
如何避免死鎖?性能

  • 爲了在單個 InnoDB 表上執行多個併發寫入操做時避免死鎖,能夠在事務開始時經過爲預期要修改的每一個元祖(行)使用 SELECT ... FOR UPDATE 語句來獲取必要的鎖,即便這些行的更改語句是在以後才執行的。
  • 在事務中,若是要更新記錄,應該直接申請足夠級別的鎖,即排他鎖,而不該先申請共享鎖、更新時再申請排他鎖,由於這時候當用戶再申請排他鎖時,其餘事務可能又已經得到了相同記錄的共享鎖,從而形成鎖衝突,甚至死鎖。
  • 若是事務須要修改或鎖定多個表,則應在每一個事務中以相同的順序使用加鎖語句。在應用中,若是不一樣的程序會併發存取多個表,應儘可能約定以相同的順序來訪問表,這樣能夠大大下降產生死鎖的機會
  • 經過 SELECT ... LOCK IN SHARE MODE 獲取行的讀鎖後,若是當前事務再須要對該記錄進行更新操做,則頗有可能形成死鎖。
  • 改變事務隔離級別。

InnoDB 默認是如何對待死鎖的?
  InnoDB 默認是使用設置死鎖時間來讓死鎖超時的策略,默認 innodblockwait_timeout 設置的時長是 50s。
如何開啓死鎖檢測?
  設置 innodbdeadlockdetect 設置爲 on 能夠主動檢測死鎖,在 Innodb 中這個值默認就是 on 開啓的狀態。


悲觀鎖:
  顧名思義,就是很悲觀,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會 block 直到它拿到鎖。
樂觀鎖:
  用數據版本(Version)記錄機制實現,這是樂觀鎖最經常使用的一種實現方式。何謂數據版本?即爲數據增長一個版本標識,通常是經過爲數據庫表增長一個數字類型的 version 字段來實現。當讀取數據時,將 version 字段的值一同讀出,數據每更新一次,對此 version 值加 1。當咱們提交更新的時候,判斷數據庫表對應記錄的當前版本信息與第一次取出來的version值進行比對,若是數據庫表當前版本號與第一次取出來的 version 值相等,則予以更新,不然認爲是過時數據。
  示例:據庫表三個字段,分別是id、value、version

select id,value,version from t where id=#{id}

  每次更新表中的value字段時,爲了防止發生衝突,須要這樣操做

update t set value=2,version=version+1 where id=#{id} and version=#{version}

樂觀鎖有什麼優勢和缺點?
  由於沒有加鎖因此樂觀鎖的優勢就是執行性能高。它的缺點就是有可能產生 ABA 的問題,ABA 問題指的是有一個變量 V 初次讀取的時候是 A 值,而且在準備賦值的時候檢查到它仍然是 A 值,會誤覺得沒有被修改會正常的執行修改操做,實際上這段時間它的值可能被改了其餘值,以後又改回爲 A 值,這個問題被稱爲 ABA 問題。


共享鎖:
  共享鎖又稱讀鎖 (read lock),是讀取操做建立的鎖。其餘用戶能夠併發讀取數據,但任何事務都不能對數據進行修改(獲取數據上的排他鎖),直到已釋放全部共享鎖。當若是事務對讀鎖進行修改操做,極可能會形成死鎖。
排它鎖:
  排他鎖 exclusive lock(也叫 writer lock)又稱寫鎖。
  若某個事物對某一行加上了排他鎖,只能這個事務對其進行讀寫,在此事務結束以前,其餘事務不能對其進行加任何鎖,其餘進程能夠讀取,不能進行寫操做,需等待其釋放。
排它鎖是悲觀鎖的一種實現:若事務 1 對數據對象 A 加上 X 鎖,事務 1 能夠讀 A 也能夠修改 A,其餘事務不能再對 A 加任何鎖,直到事物 1 釋放 A 上的鎖。這保證了其餘事務在事物 1 釋放 A 上的鎖以前不能再讀取和修改 A。排它鎖會阻塞全部的排它鎖和共享鎖。


 優化鎖的建議:

  • 儘可能使用較低的隔離級別。
  • 精心設計索引, 並儘可能使用索引訪問數據, 使加鎖更精確, 從而減小鎖衝突的機會。
  • 選擇合理的事務大小,小事務發生鎖衝突的概率也更小。
  • 給記錄集顯示加鎖時,最好一次性請求足夠級別的鎖。好比要修改數據的話,最好直接申請排他鎖,而不是先申請共享鎖,修改時再請求排他鎖,這樣容易產生死鎖。
  • 不一樣的程序訪問一組表時,應儘可能約定以相同的順序訪問各表,對一個表而言,儘量以固定的順序存取表中的行。這樣能夠大大減小死鎖的機會。
  • 儘可能用相等條件訪問數據,這樣能夠避免間隙鎖對併發插入的影響。
  • 不要申請超過實際須要的鎖級別。
  • 除非必須,查詢時不要顯示加鎖。 MySQL 的 MVCC 能夠實現事務中的查詢不用加鎖,優化事務性能;MVCC 只在 COMMITTED READ(讀提交)和 REPEATABLE READ(可重複讀)兩種隔離級別下工做。
  • 對於一些特定的事務,可使用表鎖來提升處理速度或減小死鎖的可能。

 參考文獻:

  • 高性能MySQL第三版
  • 極客時間:MySQL45講
  • 扛得住的MySQL數據庫架構:https://coding.imooc.com/class/chapter/49.html#Anchor
相關文章
相關標籤/搜索