MYSQL--三种锁
數據庫鎖設計的初衷是處理并發問題。作為多用戶共享的資源,當出現并發訪問的時候,數據庫需要合理地控制資源的訪問規則。而鎖就是用來實現這些訪問規則的重要數據結構。根據加鎖的范圍,MySQL 里面的鎖大致可以分成全局鎖、表級鎖和行鎖三類。
全局鎖:加上鎖之后整個數據庫都無法訪問。MySQL 提供了一個加全局讀鎖的方法,命令是 Flush tables with read lock (FTWRL)。
全局鎖的典型使用場景是,做全庫邏輯備份。也就是把整庫每個表都 select 出來存成文本。
不加鎖的話,備份系統備份的得到的庫不是一個邏輯時間點,這個視圖是邏輯不一致的。
之前講過,有一個辦法可以拿到一致性視圖,那就是在可重復讀隔離級別下開啟一個事務。
官方自帶的邏輯備份工具是 mysqldump。當 mysqldump 使用參數–single-transaction 的時候,導數據之前就會啟動一個事務,來確保拿到一致性視圖。而由于 MVCC 的支持,這個過程中數據是可以正常更新的。
為什么有了single-transaction還要FTWRL?
因為有的數據存儲引擎不支持事務。
比如,對于 MyISAM 這種不支持事務的引擎,如果備份過程中有更新,總是只能取到最新的數據,那么就破壞了備份的一致性。這時,我們就需要使用 FTWRL 命令了。所以,single-transaction 方法只適用于所有的表使用事務引擎的庫。如果有的表使用了不支持事務的引擎,那么備份就只能通過 FTWRL 方法。
既然要全庫只讀,為什么不使用 set global readonly=true 的方式呢?
1.readonly這個參數可以被用來做其他邏輯判斷
2.readonly設為true,之后數據庫連接異常斷開,這樣會導致整個庫長時間處于不可寫狀態,風險較高。如果是FTWRL方式,那么異常斷開后,那么 MySQL 會自動釋放這個全局鎖,整個庫回到可以正常更新的狀態。
業務的更新不只是增刪改數據(DML),還有可能是加字段等修改表結構的操作(DDL)。不論是哪種方法,一個庫被全局鎖上以后,你要對里面任何一個表做加字段操作,都是會被鎖住的。
表級鎖:表鎖,元數據鎖(meta data lock,MDL)
表鎖的語法是 lock tables … read/write。與 FTWRL 類似,可以用 unlock tables 主動釋放鎖,也可以在客戶端斷開的時候自動釋放。需要注意,lock tables 語法除了會限制別的線程的讀寫外,也限定了本線程接下來的操作對象。
舉個例子, 如果在某個線程 A 中執行 lock tables t1 read, t2 write; 這個語句,則其他線程寫 t1、讀寫 t2 的語句都會被阻塞。同時,線程 A 在執行 unlock tables 之前,也只能執行讀 t1、讀寫 t2 的操作。連寫 t1 都不允許,自然也不能訪問其他表。在還沒有出現更細粒度的鎖的時候,表鎖是最常用的處理并發的方式。
而對于 InnoDB 這種支持行鎖的引擎,一般不使用 lock tables 命令來控制并發,畢竟鎖住整個表的影響面還是太大。另一類表級的鎖是 MDL(metadata lock)。MDL 不需要顯式使用,在訪問一個表的時候會被自動加上。MDL 的作用是,保證讀寫的正確性。你可以想象一下,如果一個查詢正在遍歷一個表中的數據,而執行期間另一個線程對這個表結構做變更,刪了一列,那么查詢線程拿到的結果跟表結構對不上,肯定是不行的。因此,在 MySQL 5.5 版本中引入了 MDL,當對一個表做增刪改查操作的時候,加 MDL 讀鎖;當要對表做結構變更操作的時候,加 MDL 寫鎖。讀鎖之間不互斥,因此你可以有多個線程同時對一張表增刪改查。讀寫鎖之間、寫鎖之間是互斥的,用來保證變更表結構操作的安全性。因此,如果有兩個線程要同時給一個表加字段,其中一個要等另一個執行完才能開始執行。雖然 MDL 鎖是系統默認會加的,但卻是你不能忽略的一個機制。比如下面這個例子,我經常看到有人掉到這個坑里:給一個小表加個字段,導致整個庫掛了。
?
如何安全地給小表加字段?首先我們要解決長事務,事務不提交,就會一直占著 MDL 鎖。在 MySQL 的 information_schema 庫的 innodb_trx 表中,你可以查到當前執行中的事務。如果你要做 DDL 變更的表剛好有長事務在執行,要考慮先暫停 DDL,或者 kill 掉這個長事務。但考慮一下這個場景。如果你要變更的表是一個熱點表,雖然數據量不大,但是上面的請求很頻繁,而你不得不加個字段,你該怎么做呢?這時候 kill 可能未必管用,因為新的請求馬上就來了。比較理想的機制是,在 alter table 語句里面設定等待時間,如果在這個指定的等待時間里面能夠拿到 MDL 寫鎖最好,拿不到也不要阻塞后面的業務語句,先放棄。之后開發人員或者 DBA 再通過重試命令重復這個過程。
?
行鎖:行鎖就是針對數據表中行記錄的鎖。這很好理解,比如事務 A 更新了一行,而這時候事務 B 也要更新同一行,則必須等事務 A 的操作完成后才能進行更新。
在 InnoDB 事務中,行鎖是在需要的時候才加上的,但并不是不需要了就立刻釋放,而是要等到事務結束時才釋放。這個就是兩階段鎖協議。知道了這個設定,對我們使用事務有什么幫助呢?那就是,如果你的事務中需要鎖多個行,要把最可能造成鎖沖突、最可能影響并發度的鎖盡量往后放。
如果往后放還是會造成沖突怎么辦呢?
死鎖和死鎖檢測
當出現死鎖以后,有兩種策略:一種策略是,直接進入等待,直到超時。這個超時時間可以通過參數 innodb_lock_wait_timeout 來設置。另一種策略是,發起死鎖檢測,發現死鎖后,主動回滾死鎖鏈條中的某一個事務,讓其他事務得以繼續執行。將參數 innodb_deadlock_detect 設置為 on,表示開啟這個邏輯。
第一種方法如果時間太長,會死鎖,太短可能誤傷,因為可能只是鎖等待,就認定是死鎖
推薦第二種方法
但第二種會出現額外負擔,每個新來的被堵住的線程,都要判斷會不會由于自己的加入導致了死鎖,這是一個時間復雜度是 O(n) 的操作。
怎么解決這種負擔呢?
一種頭痛醫頭的方法,就是如果你能確保這個業務一定不會出現死鎖,可以臨時把死鎖檢測關掉。但是這種操作本身帶有一定的風險。
另一種是控制并發度。
控制并發度的一個小技巧:
可以考慮通過將一行改成邏輯上的多行來減少鎖沖突。還是以影院賬戶為例,可以考慮放在多條記錄上,比如 10 個記錄,影院的賬戶總額等于這 10 個記錄的值的總和。這樣每次要給影院賬戶加金額的時候,隨機選其中一條記錄來加。這樣每次沖突概率變成原來的 1/10,可以減少鎖等待個數,也就減少了死鎖檢測的 CPU 消耗。這個方案看上去是無損的,但其實這類方案需要根據業務邏輯做詳細設計。如果賬戶余額可能會減少,比如退票邏輯,那么這時候就需要考慮當一部分行記錄變成 0 的時候,代碼要有特殊處理。
總結
以上是生活随笔為你收集整理的MYSQL--三种锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第一章 计算机网络 4 性能指标 [计算
- 下一篇: 删除数据清理oracle表空间,orac