天天看點

Mysql讀書筆記(五):全局鎖,表鎖,行鎖知識點總結

1.鎖的分類

在Mysql中,鎖的分類包括三大類 全局鎖,表級鎖,行級鎖

2.全局鎖

全局鎖:就是對整個資料庫執行個體進行加鎖, 加鎖方法flush tables with read lock(FTWRL),整個庫為隻讀狀态,其他線程的語句 如 資料更新語句,資料定義語句和更新類事務的送出語句 都會被阻塞

使用場景: 做全庫邏輯備份,但是風險比較大

  • 如果在主庫上做備份,那麼在備份期間都不能執行更新,業務基本上停擺
  • 如果你在從庫上備份,備份期間從庫不能執行同步過來binlog,會導緻主人延遲

既然風險這麼大,在做邏輯備份時,不用可不可以? 可能會出現的問題是 在備份的時候,有更新操作,導緻資料不一緻,

解決辦法如下:

  1. mysqldump 中的一個參數 --single-transaction 可以在做邏輯備份時,生成一個事務快照視圖,熟悉MVCC的,應該對視圖不陌生,這樣可以保證在做備份時,資料是一緻的,但是有個前提是,資料表得支援 事務才可以,如果是Myisam引擎,就用不了這種 方法
  2. 還有一種是set global readonly=true的方式 也可以全局鎖的效果,但是不建議用,原因有如下幾點
  3. readonly的值有的時候會在程式中使用,來判斷是主庫還是從庫,如果修改此值,影響可能比較嚴重
  4. 在異常處理下有差别 ,如果執行 flush talbes with read lock,在用戶端發 生異常斷開後,那麼Mysql會自動釋放這個全局鎖,整個庫可以回到正常的更新的狀态,如果使用readonly之後,如果用戶端發生異常,則資料庫一直會保持readonly狀态,會導緻整個庫會處理不可寫狀态,風險較高

釋放全局鎖:指令: unlock tables

3.表級鎖

3.1表級鎖分類

表級鎖分類包括 表鎖和中繼資料鎖(MDL)

3.2表鎖

表鎖文法

加鎖: lock tables 表名 read/write

釋放鎖:unlock tables,也可以在用戶端斷開的進修自動釋放

lock tables 文法除了會限制其他線程的讀寫外,也會限制本線程接下來的操作對象

3.3中繼資料鎖(MDL)

中繼資料鎖(metadata lock),MDL不需要顯示指定,在通路一個表時候會被自動加上,作用是保證讀寫的正确性,特點如下:

  • 讀鎖之間不互斥,是以可以有多個線程同時對一張表做增删改查
  • 讀寫鎖,寫鎖之間是互斥的,用來保證變更表結構操作的安全性

3.4修改表結構,或者加索引有可能會出現的問題

在修改表結構或索引時,需要掃描全表 的資料,如果 線上上給大表 操作的話,可能會對服務有影響,修改小表也有可能出現問題
Mysql讀書筆記(五):全局鎖,表鎖,行鎖知識點總結
可以看到 session A 先啟動,會對表 加一個MDL鎖, 由于session2也是讀鎖,是以可以正常執行,但是session3 需要寫鎖,是以被阻塞,同時還會影響session3後面的 讀鎖請求,如果請求很大,這個庫的線程很快就會被占滿,這是很危險的

知識點:

事務中 MDL鎖,在語句執行時開始申請,語句結束後并不會馬上釋放,而是等整個事務送出後再釋放,這也是為什麼不建議出現長事務的原因

那如何安全的給小表添加字段呢?

解決辦法:

在MariaDB 和AliSql中都支援 ddl nowait/wait n 的文法

alter table t1 nowait add column
 alter table t1 wait n add column           

4.行級鎖

innodb引擎支援行級鎖

4.1 二階段鎖協定

在Innodb中 行鎖是在需要的時間才加上的,但并不是在不需要的時候就釋放了,而是要等到事務結束後才釋放,也就是二階段鎖協定

最佳實踐

如果你的事務中需要鎖多個行,要把最有可能生成鎖沖突,最有可能影響并發度的鎖盡量向後放

解釋一下上面的結論

比方2種扣減庫存的方案如下:

方案1:begin;// 扣減庫存update t_inventory set count=count-5 where id= ${id} and count>=5;// 鎖住使用者賬戶表select * from t_user_account where user_id=123 for update;// 插入訂單記錄insert into t_trans; commit;

方案2:begin;// 鎖住使用者賬戶表select * from t_user_account where user_id=123 for update;// 插入訂單記錄insert into t_trans;// 扣減庫存update t_inventory set count=count-5 where id=${id} and count>=5;commit;

兩者方案的時序如下圖所示:

Mysql讀書筆記(五):全局鎖,表鎖,行鎖知識點總結

由于庫存往往是最重要的熱點,是整個系統的瓶頸。那麼如果采用第二種方案的話,tps應該理論上能夠提升3rt/rt=3倍。這還僅僅是業務就隻有三條SQL的情況下,多一條sql就多一次rt,就多一倍的時間。

根據兩階段鎖協定,不論你怎樣安排語句順序,所有的操作需要的行鎖都是在事務送出的時候才釋放的。是以,如果你把更新庫存 安排在最後,那麼庫存這一行的鎖時間就最少。這就最大程度地減少了事務之間的鎖等待,提升了并發度。

4.2死鎖及死鎖檢測

死鎖:當并發系統中不同線程出現循環資源依賴,涉及的線程都在等待别的線程釋放資源時,就會導緻這幾個線程都進入無限等待的狀态,稱為死鎖,如下圖

Mysql讀書筆記(五):全局鎖,表鎖,行鎖知識點總結

這時候,事務A在等待事務B釋放id=2的行鎖,而事務B在等待事務A釋放id=1的行鎖,這種情況,就進入了死鎖狀态

4.3 死鎖解決辦法

  1. 一種政策是,直接進行等待,直到逾時, 由innodb_lock_wait_timeout 參數控制,預設值為50s,生産環境不建議用
  2. 第二種政策是 發起死鎖檢測,發現死鎖後,主動復原死鎖鍊條中的某一個事務,讓其他事務得以繼續執行 ,由innodb_deadlock_detect參數控制 ,設定on 表示開啟這個邏輯,預設為開

如果用第2種政策時,可能會出現熱點行更新導緻的性能問題,cpu 消息接近100% ,但是整個資料庫,每秒就執行不到100個事務的情況出現,原因是什麼?

原因如下:

每當一個事務被鎖時,都會檢查它所依賴的線程有沒有被别人鎖住,如此循環,最後判斷是否出現死鎖,如果有多個并發線程要同時執行更新同一行,那麼死鎖檢測操作會是非常大的指數級的操作,這期間要消耗大量的cpu資源,是以就會出現上面說的那種情況

解決辦法

  1. 直接關掉死鎖檢測,風險很大,不建議
  2. 控制并發度,可以在用戶端,中間件中,服務端做并發度控制
  3. 從業務方面調整,目标就是為了減少死鎖沖突,讓資源不集中處理,減少鎖沖突

繼續閱讀