MySQL學習之鎖


數據庫鎖的設計初衷是爲了解決併發問題。數據庫作爲一個多用戶共享的資源,當出現併發訪問時就需要數據庫合理的控制訪問規則,而鎖就是來實現訪問規則的一直方式

根據鎖的範圍劃分,在MySQL中可以分爲全局鎖、表級鎖和行鎖三類

1.全局鎖

全局鎖顧名思義就是對整個數據庫實例加鎖,全局鎖的典型使用場景就是做全局的邏輯備份,備份過程中整個庫處於只讀狀態

使整個庫處於只讀狀態可以執行命令flush tables with read lock(FTWRL),執行這個命令後其他線程執行數據更新語句(數據的增刪改)數據定義語句(包括建表、修改表結構) 時都會被阻塞

另一種方式是使用官方自帶的邏輯備份工具mysqldump使用-- single transacation參數,原理是備份前開啓一個事務拿到一致性視圖,根據事務的隔離級別,在可重複讀級別下這個視圖中讀的數據總是與開啓事務時一致。single transacation只適用於所有表都使用的事務引擎的庫

2.表級鎖

在MySQL裏表級鎖有表鎖和元數據鎖(meta data lock)MDL

2.1.表鎖

表鎖的語法是:

-- 加鎖
lock tables tableName1 read/write,tableName2 read/write ... ;
-- 釋放鎖
unlock tables

舉一個例子,如果一個線程A執行lock tables t1 read,t2 write之後,其他線程只能讀t1,寫t1和讀寫t2都會被阻塞。A線程也只能讀t1和讀寫t2,其他表不能訪問

當使用lock tables控制併發時鎖住了整個表,從而帶來了嚴重的性能問題,InnoDB支持行鎖一般就不使用lock tables控制併發了,將lock tables和unlock tables改成begin和commit

2.2.元數據鎖

MDL是在訪問表時自動加上,不需要顯示指定。MDL的作用是保證數據讀寫的正確性。當一個線程查詢出一個列表數據時,同時另一個線程刪除了一個字段,這樣造成數據不一致顯然這種情況時不能接受的

當對一個表進行增刪改時加MDL讀鎖,當改變表結構時加MDL寫鎖

  • MDL讀鎖之間不互斥,多個線程可以對同一張表做增刪改查操作
  • MDL寫鎖和讀寫鎖之間是互斥的,用來保證變更表結構時數據操作的安全性。當兩個線程都需要變跟表結構時一個線程必須要等待另一個線程執行變更完之後能才能執行

當我們需要跟一個表添加字段時,首先要解決長事務,因爲事務會一直佔着MDL鎖直到事務提交纔會釋放,當這個表是熱點表又需要在這個表上加字段,比較理想的機制是在執行alter table語句裏增加等待時間,MariaDB引擎已經實現

alter table t1 nowait add column ...
alter table t2 wait add column ...

3.行鎖

顧明思意行鎖就是數據表中行的鎖,當兩個事務A,B同時更新同一行時,只能等事務A執行完之後事務B才能去執行更新操作

InnoDB的行鎖是針對索引加的鎖,不是針對記錄的鎖,並且索引不能失效,否則會從行鎖升級成表鎖

  • 行鎖的優勢:鎖的粒度小,發生鎖衝突的概率低,處理併發能力強
  • 行鎖的劣勢:開銷大,加鎖慢,會出現死鎖情況
  • 加鎖的方式:當執行INSERT,UPDATE,DELETE時InnoDB會自動加排他鎖,SELECT不會加任何鎖,需要顯示加鎖:

共享鎖:select * from tableName where ... lock in share more
排他鎖:select * from tableName where ... for update

3.1.兩階段鎖協議

首先我們先了解一下兩階段鎖協議,在InnoDB事務中,行鎖是在需要的時候在加上,但並不是在不需要的時候則釋放,需要等到事務提交之後才釋放。這個就是兩階段鎖協議

瞭解這個協議之後,我們使用事務時應該注意,當一個事務需要多個鎖時,把最有可能造成衝突,最有可能影響併發度的鎖應該儘量往後放

3.2.共享鎖

共享鎖也稱讀鎖,多用於判斷數據是否存在

  1. 多個事務都可以加讀鎖,可以同時讀取同一記錄不受影響。
  2. 不允許其他事務加排他鎖
  3. 當事務修改數據時,必須要等待先執行的事務commit然後在執行更新操作,不然當併發時很容易造成死鎖。

3.3.排他鎖

排他鎖也稱讀寫鎖,獨佔鎖,當前操作沒有執行完之前,會阻塞其他的讀鎖和寫鎖

當執行INSERT,UPDATE,DELETE時,InnoDB會自動加排他鎖

4.死鎖和死鎖檢測

當出現併發時不同的線程出現循環依賴資源,涉及到的線程都在等待其他線程釋放資源,從而造成這些線程出現無限等待的狀態,這種情況稱爲死鎖。當出現死鎖狀態時有兩種處理策略:

  • 一種是直接進入等待,直到超時,InnoDB可以通過參數innodb_lock_wait_timeout來設置,默認是50s
  • 另一種是發起死鎖檢測,發現死鎖後主動回滾死鎖鏈中的某一條事務,讓其他事務可以繼續執行。可以使用參數innodb_deadlock_detect=on,表示死鎖檢測開啓

怎麼解決熱點行更新造成的性能問題
當死鎖檢測開啓時,當併發數達到1000同時去更新同一條數據時,檢測死鎖這個操作就是100W級別的,造成CPU負載很高,那麼每秒執行的事務也很低。怎麼去解決這種性能問題了

  • 哪裏造成的性能問題就從哪裏解決,在確保業務部會出現死鎖的情況下可以關閉死鎖檢測。業務中如果出現死鎖時就回滾然後重試這樣是無損的,當然死鎖也會出現大量的超時,這就對業務有損了
  • 控制併發度,如在客戶端控制更新同一行的併發度,當客戶端多時服務端的併發度也隨之增加。當然可以修改數據庫的服務端進行控制,對於更新相同行時排隊進入引擎,這樣InnoDB內部就不會有那麼高的死鎖檢測了,一般小團隊沒有能力做到
  • 從表設計上優化,可以通過更新一行的操作改成邏輯上的多行來減少衝突。如在庫存表中,一個商品的庫存可以放在10條記錄中,商品實際庫存爲幾條記錄之和,這樣每次操作庫存發生衝突的概率爲原來的1/10,減少了鎖的等待個數,也就減少了死鎖檢測帶來的CPU消耗。這種方案就需要控制數據的邊界了

參考文獻

  • 極客時間《MySQL實戰45講》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章