MySQL 中有哪些鎖?
數據庫中鎖的設計初衷處理併發問題,作爲多用戶共享資源,當出現併發訪問的時候,數據庫需要合理控制資源訪問規則。鎖就是實現這些訪問規則中的重要數據。
鎖的分類
根據加鎖範圍,MySQL 裏面的鎖可以分成全局鎖、表級鎖、行鎖三類。
全局鎖
全局鎖,就是對整個數據庫實例加鎖,MySQL 提供了一個加全局讀鎖的方法,命令是:
Flush tables with read lock (FTWRL)
當需要整個庫只讀狀態的時候,可以使用這個命令,之後其他線程的:數據更新語句(增刪改),數據定義語句(建表,修改表結構)和更新事務的提交語句將會被阻塞。
全局鎖的使用場景
全局鎖的定型使用場景,做全庫邏輯備份。也就是把整個庫每個表都 Select 出來,然後存成文本。
如何整個庫都只讀,會有什麼問題?
- 如果你在主庫上備份,那麼在備份期間都不能執行更想,業務就基本上停擺。
- 如果在從庫上備份,那麼備份期間從庫不能執行主庫同步過來的 binlog ,會導致從延遲。
既然要全庫只讀, 爲什麼不使用set global readonly=true的方式呢?
readonly 方式也可以讓全庫進入只讀狀態,但我還是會建議你用FTWRL方式, 主要有兩個原因:
- 一是, 在有些系統中, readonly的值會被用來做其他邏輯,比如用來判斷一個庫是主庫還是備庫。 因此,修改global變量的方式影響面更大, 我不建議你使用。
- 二是, 在異常處理機制上有差異。如果執行FTWRL命令之後由於客戶端發生異常斷開, 那麼MySQL會自動釋放這個全局鎖, 整個庫回到可以正常更新的狀態。 而將整個庫設置爲readonly之後, 如果客戶端發生異常, 則數據庫就會一直保持readonly狀態, 這樣會導致整個庫長時間處於不可寫狀態, 風險較高
表級別鎖
MySQL 裏面表級別的鎖有兩種:一種是表鎖,一種是元數據鎖(meta data lok, MDL)。
表鎖的語法是 :
lock tables ... read/write
與 FTWRL 類似,可以使用 unlock tables 主動釋放鎖,也可以在客戶端斷開的時候自動釋放。需要注意的是,lock tables語法除了會限制別的線程的讀寫外,也限定了本線程接下來的操作對象。
MDL 表級鎖
MDL 不需要顯示使用,在訪問一個表的時候自動加上, MDL 保證讀寫的正確性,也就是說在查詢數據時,不允許有其他線程對這個表結構做變更。
什麼操作會加 MDL 鎖?
在MySQL 5.5版本中引入了MDL, 當對一個表做增刪改查操作的時候,加MDL讀鎖; 當要對錶做結構變更操作的時候,加MDL寫鎖。
- 讀鎖之間不互斥,因此可以有多個線程同時對一張表增刪改查。
- 讀寫之間、寫鎖之間是互斥的,用來保證變更表結構操作的安全性,如果有兩個線程要同時給一個表加字段,其中一個要等另外一個執行完才能執行。
更改表結構要注意哪些?
給一個表加字段, 或者修改字段, 或者加索引, 需要掃描全表的數據。 在對大表操作的時候, 你肯定會特別小心, 以免對線上服務造成影響。 而實際上, 即使是小表, 操作不慎也會出問題,導致整個庫的線程爆滿。
舉個例子
我們來看一下下面的操作序列, 假設表t是一個小表。
- session A先啓動, 這時候會對錶t加一個MDL讀鎖。 由於session B需要的也是MDL讀鎖, 因此可以正常執行。
- session C會被blocked, 是因爲session A的MDL讀鎖還沒有釋放, 而session C需要MDL寫鎖, 因此只能被阻塞,讀寫鎖互斥。
- 如果只有session C自己被阻塞還沒什麼關係, 但是之後所有要在表t上新申請MDL讀鎖的請求也會被session C阻塞。 前面我們說了,所有對錶的增刪改查操作都需要先申請MDL讀鎖, 就都被鎖住, 等於這個表現在完全不可讀寫了。
如果某個表上的查詢語句頻繁, 而且客戶端有重試機制,也就是說超時後會再起一個新session 再請求的話, 這個庫的線程很快就會爆滿。 事務中的MDL鎖, 在語句執行開始時申請, 但是語句結束後並不會馬上釋放, 而會等到整個事務提交後再釋放。
怎麼解決這個 更改表結構問題
比較理想的機制是, 在alter table語句裏面設定等待時間, 如果在這個指定的等待時間裏面能夠拿到MDL寫鎖最好, 拿不到也不要阻塞後面的業務語句, 先放棄。
ALTER TABLE tbl_name NOWAIT add column ...
ALTER TABLE tbl_name WAIT N add column ...