MySQL鎖問題 - MyISAM與InnoDB的鎖機制
目錄
一、MySQL鎖概述
MySQL的鎖機制相對於其他資料庫而言比較簡單,其最顯著的特點是不同的儲存引擎支援不同的鎖機制
1、表級鎖和行級鎖的特點
鎖型別 | 開銷 | 加鎖速度 | 死鎖 | 粒度 | 發生衝突概率 | 併發度 |
---|---|---|---|---|---|---|
表級鎖 | 小 | 快 | 無 | 大 | 最高 | 最低 |
行級鎖 | 大 | 慢 | 有 | 最小 | 最低 | 最高 |
2、表級鎖和行級鎖的適用場景
僅從鎖的角度來講:
鎖型別 | 適合場景 | 例項 |
---|---|---|
表級鎖 | 以查詢為主,只有少量按索引更新資料的應用 | web |
行級鎖 | 有大量按索引條件併發更新少量不同資料,同時又有併發查詢的用用 | OLTP |
二、MyISAM表鎖
MyISAM只支援表鎖
1、查詢表級鎖爭用情況
可以通過檢查table_locks_waited
和table_locks_immediate
狀態變數來分析系統上的表鎖定爭奪
show status like 'table%';
如果table_locks_waited
的值比較高,說明存在比較嚴重的表鎖爭用情況
2、MySQL表級鎖的鎖模式
MySQL的表級鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨佔寫鎖(Table Write Lock)
鎖模式/請求鎖模式是否相容 | None | 讀鎖 | 寫鎖 |
---|---|---|---|
讀鎖 | 是 | 是 | 否 |
寫鎖 | 是 | 否 | 否 |
MyISAM表的讀操作,不會阻塞其他讀請求,但會阻塞寫請求
MyISAM表的寫操作,會阻塞其他的讀寫請求
MyISAM表的讀、寫操作之間,以及寫操作之間是序列的,當一個執行緒獲得對一個表的寫鎖後,其他執行緒的讀寫操作都會阻塞,直到鎖釋放
3、如何加表鎖
MyISAM在執行SELECT
語句遷,會自動給涉及的所有表加讀鎖
在執行UPDATE
DELETE
INSERT
前,會自動給涉及的表加寫鎖
以上過程不需要使用者干預,一般不需要直接用LOCK TABLE
命令給MyISAM表顯式加鎖
MySQL不支援鎖升級,執行
LOCK TABLE
後,只能訪問顯式加鎖的表,不能訪問未加鎖的表,且讀鎖只能讀不能寫
自動加鎖的情況下也是如此,MyISAM總是一次性獲得SQL語句需要的全部鎖,這也正式MyISAM不會出現死鎖的原因
當使用LOCK TABLE
時,不僅需要一次鎖定用到的所有表,同一個表在SQL中出現多少次,都要按照別名鎖定多少次
lock table post read;
select a.post_name,a.post_id,b.post_name,b.post_id from post a,post b where a.post_name = b.post_name and a.post_id = 1;
#ERROR 1100 (HY000):Table 'a' was not locked with LOCK TABLES
##需要對別名分別鎖定
lock table post as a,post as b;
4、併發插入 Concurrent Inserts
總體而言,MyISAM表的讀寫操作是序列的;但在一定條件下MyISAM表也支援查詢和插入操作的併發進行
通過修改MyISAM系統變數concurrent_insert
的值(預設為1)來控制併發出入的行為
- 0:不允許併發插入
- 1:如果MyISAM表中沒有空洞,在一個程序讀的同時,允許另一個程序在表尾插入資料
- 2:無論有沒有空洞,都允許表尾併發插入資料
空洞:表的中間被刪除的行
可以利用MyISAM的併發插入特性來解決對同一表查詢和插入的鎖爭用問題
例如,將
concurrent_insert
的值設定為2,再定期在系統空閒時執行OPTIMIZE TABLE
語句來整理空間碎片,收回因刪除記錄而產生的中間空洞
5、MyISAM的鎖排程
MyISAM的讀鎖和寫鎖是互斥的,讀寫操作是序列的
那麼當一個程序請求一個表的讀鎖,另一個程序請求同一表的寫鎖時如何排程呢?
答案是寫程序先獲得鎖
不僅如此,即使在佇列中讀請求位於寫請求前,寫請求也會插隊優先獲得鎖,這是因為MySQL認為寫請求通常比讀請求重要,這也是MyISAM表不太適合有大量更新操作和查詢的原因,大量的查詢會導致讀請求持續阻塞在佇列中,不過MyISAM支援通過一些設定來調節排程行為
- 通過指定啟動引數
low-priority-updates
使MyISAM引擎預設給予讀請求以優先的權利 - 通過執行命令
SET LOW_PRIORITY_UPDATES=1
使該連結發出的更新請求優先順序降低 - 通過指定
INSERT
、UPDATE
、DELETE
語句的LOW_PRIORITY
屬性,降低該型別語句的優先順序
通過以上3種方法,可以解決查詢相對重要的應用中讀鎖嚴重阻塞的問題
MySQL也提供了一種這種的解決辦法:
給系統引數max_write_lock_count
設定一個合理的值,當寫鎖達到這個值後,MySQL會降低寫請求的優先順序
三、InnoDB鎖問題
InnoDB和MyISAM最大的不同有兩點:支援事務、支援行級鎖
1、背景知識
事務及其ACID屬性
- 原子性 Atomicity:事務是一個原子操作單元,其對資料的修改,要麼全都執行,要麼全都不執行
- 一致性 Consistent:在事務開始和完成時,資料都必須保持一致性狀態,即所有相關的資料規則必須應用於事務修改,以保持資料的完整性;事務結束時,所有的內部資料結構,如B樹索引或雙向連結串列,也都必須是正確的
- 隔離性 Isolation:事務處理過程中對外界是不可見的,同時外部對事務的執行也沒有影響
- 永續性 Durable:事務結束後,對資料的修改是永久的
併發對事務處理的影響
併發事務處理可以大大增加資源利用率,提高資料庫系統的事務吞吐量,但也會帶來以下問題
- 更新丟失:由於事務的隔離性,當兩個事務需要處理同一行資料時,他們並沒有感知,後提交的事務會覆蓋之前的事務處理結果,解決方案是使這兩個事務變成序列
- 髒讀:一個事務在對一條資料做修改的同時,另一個事務來讀這條資料,這條資料就處於不一致狀態,第二個事務讀取了髒資料,並進一步處理,就會產生未提交的資料依賴關係,這種現象被稱為髒讀
- 不可重複讀:一個事務在讀取某些資料後的一個時間點,再次讀以前讀過的資料,卻發現數據已經改變或刪除了
- 幻讀:前置條件同不可重複讀,事務在以相同條件讀曾經讀過的資料時,發現其他事務插入了滿足查詢條件的新資料
事務的隔離級別
更新丟失的情況不止要靠資料庫事務控制器解決,需要對資料加必要的鎖來解決
髒讀、不可重複讀、幻讀都是資料庫讀一致性問題,必須由資料庫提供一定的事務隔離機制來解決
- 在讀資料前,對其加鎖,阻止其他事務對資料進行修改
- 不加鎖,通過一定機制生成資料庫在請求時間點的資料快照,並用這個快照來提供一定級別(語句級或事務級)的一致性讀取,這種技術被稱為MVCC或MCC(MutiVersion Concurrency Control),多版本資料庫
資料庫的事務隔離越嚴格,併發副作用越小,但由於隔離的實質是序列化,往往付出的代價也越大
為了解決併發與隔離的矛盾,ISO/ANSI SQL92定義了4個事務隔離級別
隔離界別 / 讀一致性級別及副作用 | 讀資料一致性 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|---|
未提交讀 | 最低級別,只能保證不讀取物理上損壞的資料 | 是 | 是 | 是 |
已提交讀 | 語句級 | 否 | 是 | 是 |
可重複讀 | 事務級 | 否 | 否 | 是 |
可序列化 | 最高級別,事務級 | 否 | 否 | 否 |
2、獲取InnoDB行鎖爭用情況
可通過檢查InnoDB_row_lock
狀態變數來分析系統上的行鎖爭奪情況
show status like 'innodb_row_lock%';
通過查詢information_schema資料庫中的表瞭解鎖等待情況
select * from innodb_locks\G
通過設定InnoDB Monitors觀察鎖衝突情況
CREATE TABLE innodb_monitor(a INT) ENGINE=INNODB;
show engine innodb status\G
監視完成後需要及時關閉監視器,否則會導致.err檔案變得非常巨大
DROP TABLE innodb_monitor;
3、InnoDB的行鎖模式及加鎖方法
InnoDB實現了以下兩種型別的行鎖
- 共享鎖 S:允許一個事務去讀一行,阻止其他事務獲取相同資料集的排它鎖
- 排它鎖 X:只允許獲得排它鎖的資料更新資料,阻止其他的共享鎖和排它鎖
另外為了允許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB還有兩種內部使用的意向鎖,均為表鎖,意向鎖是InnoDB自動加的,不需要使用者干預
UPDATE
INSERT
DELETE
語句,自動加排它鎖
普通的SELECT
語句不會自動加鎖
- 意向共享鎖 IS:事務打算加共享鎖前,需要獲得意向共享鎖
- 意向排它鎖 IX:事務打算加排它鎖前,需要獲得意向排他鎖
當前鎖模式/是否相容/請求鎖模式 | X | IX | S | IS |
---|---|---|---|---|
X | 衝突 | 衝突 | 衝突 | 衝突 |
IX | 衝突 | 相容 | 衝突 | 相容 |
S | 衝突 | 衝突 | 相容 | 相容 |
IS | 衝突 | 相容 | 相容 | 相容 |
如果一個事務請求的鎖模式與當前鎖模式相容,InnoDB就將請求的鎖授權予該事務;反之如果不相容,就要等鎖釋放
事務可以通過以下語句顯式給記錄集加共享鎖或排他鎖
#共享鎖
SELECT * FROM tb_name WHERE ... LOCK IN SHARE MODE;
#排他鎖
SELECT * FROM TB_NAME WHERE ... FOR UPDATE;
#當前事務加了共享鎖後再對資料更新,很有可能造成死鎖,如果需要更新則應該加排他鎖
4、InnoDB行鎖實現方式
InnoDB行鎖是通過給索引上的索引項加鎖來實現的,如果沒有索引,InnoDB將通過隱藏的聚簇索引來對記錄加鎖;InnoDB行鎖分為3種情形
- Record lock:對索引項加鎖
- Gap lock:對索引項之間的間隙、第一條記錄前的間隙或最後一條記錄後的間隙加鎖
- Next-key lock:前兩種的組合,對記錄和間隙加鎖
InnoDB這種行鎖實現特點意味著:如果不通過索引條件檢索資料,就會鎖表中的所有記錄,實際效果和表鎖一樣
- 在不通過索引查詢時,InnoDB會鎖定表中所有記錄
- 由於MySQL的行鎖是針對索引加的鎖,所以當訪問不同行的記錄,但使用相同的索引鍵,會出現鎖衝突
- 當表中有多個索引時,不同是事務可以使用不同的索引鎖定不同的行,不論索引的型別,InnoDB都會使用行鎖來鎖定資料
- 即便使用了索引欄位,MySQL也可能認為掃全表效率更高,此時即會鎖表,執行前可以檢視執行計劃
5、Next-Key鎖
當使用範圍條件查詢時,InnoDB會對符合條件的資料加鎖;對於範圍內但不存在的記錄,叫做間隙(GAP),InnoDB也會對這個間隙加鎖,這就是Next-Key鎖
InnoDB使用Next-Key鎖的目的,一方面是為了防止幻讀,另一方面是為了滿足恢復和複製的需要
如果使用相等條件請求給一個不存在的記錄加鎖時,InnoDB也會使用Next-Key鎖