MySQL鎖原理淺談
鎖的分類
- 表鎖:開銷小,加鎖快,不會死鎖,粒度大,衝突率高,併發低。
- 行鎖:開銷大,加鎖慢,會死鎖,粒度小,衝突率低,併發高。
- 頁鎖:處於表鎖和行鎖之間,會死鎖。
鎖的適用場景
- 表鎖:更適用於查詢為主,按少量索引條件更新。
- 行鎖:更適用於大量按索引併發更新少量不同資料,同時又有併發查詢。
MyISAM表鎖
- 檢視鎖爭用相關引數:show status like 'table%';
- Table_locks_waited的值越高表示表鎖爭用越高。
- MyISAM表的讀操作,會阻塞同表的其他讀請求,會阻塞同表寫請求;
- 寫操作會阻塞同表的讀請求和寫請求。
- 讀與寫、寫與寫之間序列,持鎖執行緒可對錶更新,其他執行緒讀/寫都會等待,直到鎖釋放。
MyISAM寫阻塞讀的例子
session 1 | session 2 |
---|---|
lock table user write; | |
select * from user; //返回查詢結果 | select * from user; //被阻塞,等待鎖被釋放 |
unlock tables; | 獲得鎖,返回查詢結果 |
注:
- lock tables時,要一次性鎖定用到的所有表
- 對別名也需要鎖定,如:lock table user as a read, user as b read;
MyISAM讀阻塞寫例子
session 1 | session 2 |
---|---|
lock table user read; | |
可查詢:select * from user; | 可查詢:select * from user; |
不能查詢未鎖定的表:select * from goods; //Table 'goods' was not locked with Lock Tables | 能查詢/更新未鎖定的表 |
當前session更新鎖定表會報錯,Read Lock | 更新鎖定表會等待 |
Unlock tables; | 獲得鎖,更新完成 |
MyISAM併發插入
系統變數 concurrent_insert:用於控制併發插入行為
- 0 不允許併發插入
- 1 表中沒有被刪除的行(即沒有空洞),則允許一個程序讀,另一個程序在表尾插入(預設設定)
- 2 表中不論是否存在空洞,都允許在表尾併發插入
MyISAM讀寫併發
session 1 | session 2 |
---|---|
lock table user read local; | |
當前session無法對該表更新或插入 | 可以插入,但更新需要等待鎖釋放 |
無法訪問其他session插入的資料 | |
unlock tables; | 獲得鎖,更新完成 |
可以查到其他session插入的資料 |
注:
- 利用併發插入可以解決應用對同一個表查詢和插入的鎖爭用;
- 將cocurrent_insert設定為2,定期OPTIMIZE TABLE來整理空間碎片,回收刪除記錄產生的空洞。
MyISAM鎖排程
- 讀鎖與寫鎖互斥;
- 讀操作與寫操作序列;
- 寫程序先獲得鎖,即使讀請求先到佇列,也會被寫請求插隊,因為mysql認為寫比讀要重要(因此MyISAM不適合有大量更新/插入操作)。
調節MyISAM鎖排程行為
- low-priority-updates,給予讀優先權利;
- SET LOW-PRIORITY_UPDATES=1,降低更新請求優先順序;
- 指定INSERT、UPDATE、DELETE的LOW-PRIORITY屬性,降低該語句優先順序。
解決讀寫衝突的方法:
- 系統引數 max_write_lock_count 設定合理值,表的讀鎖達到設定閾值後,mysql就將寫請求優先順序降低。
- 一些需要長時間執行的讀操作,需要拆分為多條短select sql,複雜查詢放在資料庫空閒時段進行,比如夜間執行。
InnoDB與MyISAM最大區別:
- 支援事務;
- 行級鎖。
事務 - Transaction
事務操作 | 描述 |
---|---|
BEGIN 或者 START TRANSACTION | 開始事務 |
COMMIT | 提交事務 |
ROLLBACK | 回滾結束事務,撤銷進行中的所有未提交的修改 |
SAVEPOINT identifier | 設定儲存點 |
RELEASE SAVEPOINT identifier | 事務回滾到儲存點 |
ROLLBACK TO identifier | 撤銷儲存點 |
SET TRANSACTION = {READ UNCOMMITED,READ COMMITED,REPEATABLE READ,SERIALIZABLE} | 設定事務隔離級別 |
SET AUTOCOMMIT = {0,1} | 禁止/開啟自動提交 |
事務的特性
- A - Atomicity 原子性:全執行/全不執行
- C - Consistent 一致性:資料狀態一致
- I - Isolation 隔離性:事務處理過程中的中間狀態對外不可見,不受外部併發操作影響
- D - Durable 永續性:事務完成後對資料修改是永久性的
併發事務問題 | 描述 | 解決方案 |
---|---|---|
更新丟失 | 兩個事務對同一行資料修改,先提交的被後提交的覆蓋 | 應用程式對要更新的資料加鎖 |
髒讀 | A事務改一行資料,B事務讀到了A的改動“髒”資料,A回滾則B的資料有問題 | 資料庫事務隔離,解決讀一致性問題:1、讀之前加鎖,防止其他事務對資料修改;2、不加鎖,生成快照,多版本併發控制 |
不可重複讀 | 一個事務多次讀取同一資料發現被改變/刪除 | 同上 |
幻讀 | 一個事務按先前的條件查詢,發現其他事務插入了滿足條件的新資料 | 同上 |
注:
事務隔離級別越高,併發副作用越小,代價越高,因為事務隔離從某種程度上說使得事務序列化。
SQL/">MySQL事務隔離級別
隔離級別/併發問題 | 讀一致性 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|---|
未提交讀 | 最低 | 有 | 有 | 有 |
已提交讀 | 語句級 | 無 | 有 | 有 |
可重複讀 | 事務級 | 無 | 無 | 有 |
可序列化 | 最高 | 無 | 無 | 無 |
獲取InnoDB行鎖爭用情況
- show status like 'innodb_row_lock%';
- 鎖爭用嚴重時,InnoDB_row_lock_waits和InnoDB_row_lock_time_avg值較大。
InnoDB行鎖型別
行鎖型別 | 描述 |
---|---|
共享鎖 S | 允許事務讀一行,阻止其他事務獲得排他鎖 |
排他鎖 X | 允許事務更新資料,阻止其他事務獲得共享讀鎖和排他寫鎖 |
意向共享鎖 IS | 事務打算給行加共享鎖,先取得表IS鎖 |
意向排他鎖 IX | 事務打算給行加排他鎖,先取得表IX鎖 |
請求鎖模式是否相容當前鎖模式 | X | IX | S | IS |
---|---|---|---|---|
X | 否 | 否 | 否 | 否 |
IX | 否 | 是 | 否 | 是 |
S | 否 | 否 | 是 | 是 |
IS | 否 | 是 | 是 | 是 |
注:
- 含I的鎖與含I的鎖相容;
- 單X與任何鎖不相容;
- 單S與含X的鎖不相容;
- 若一個事務請求的鎖模式與當前的鎖相容,InnoDB將請求的鎖授予該事務,不相容就要等到鎖釋放;
- 意向鎖是InnoDB自動加的,DELETE、UPDATE、INSERT,InnoDB會自動加X鎖,普通SELECT,InnoDB不加任何鎖。
手動加鎖的方法
- 共享鎖(S):SELECT * FROM user LOCK IN SHARE MODE;
-
排他鎖(X):SELECT * FROM user FOR UPDATE;
注:
- SELECT * FROM ... LOCK IN SHARE MODE; //若當前事務加了讀鎖,進行更新會死鎖
- SELECT * FROM ... FOR UPDATE; //一個事務加了寫鎖,其他事務加鎖操作需要等待
- InnoDB行鎖是通過給索引上的索引項加鎖來實現的,只有通過索引條件檢索,才會使用行級鎖,否則會用表鎖;
-
分析鎖衝突時,檢查SQL執行計劃(利用explain),以確認是否真正走了索引,例如:SELECT * FROM user WHERE name = 123; //name欄位是varchar型別且有索引,但條件中用了int型,型別能自動轉換,但會進行全表掃描。
間隙鎖(Next-key Lock)
概念描述
用範圍而非等值搜尋資料,並且請求共享/排他鎖時,InnoDB會對所有符合條件的已有記錄的索引項加鎖,對鍵值在範圍內但不存在的記錄,即GAP-間隙,也會加鎖。
例如:
- user表,id從1~100共100個,執行:
- SET AUTOCOMMIT = 0;
- SELECT * FROM id > 99 FOR UPDATE;
-
會對id等於100的記錄的索引項加鎖,對id大於99的間隙加鎖。
作用:
- 滿足隔離級別要求,防止幻讀;
-
滿足恢復和複製需要(MySQL通過BINLOG錄入執行成功的INSERT、UPDATE、DELETE等更新語句)
存在的問題:
按範圍加鎖機制會阻塞符合條件範圍內的鍵值併發插入,造成鎖等待。
解決方法:
優化業務邏輯,儘量用相等條件來檢索資料。
注:
相等條件檢索一個不存在記錄加鎖時,InnoDB也會使用間隙鎖。例如:
- 對上面的user表,執行:
- SET AUTOCOMMIT = 0;
- SELECT * FROM id = 101 FOR UPDATE;
-
再在另一個 MySQL Session 中執行 INSERT INTO
user
(id
,name
,password
,description
)
VALUES
(101, 'clive', '123456', 'psw'); //查詢被阻塞,進入等待直至鎖釋放
死鎖的概念
死鎖是指多個事務在統一資源上,出現相互佔用,並請求鎖定對方佔用的資源,從而導致惡性迴圈的現象。
MyISAM和InnoDB在死鎖上的區別
- MyISAM不會出現死鎖,因為MyISAM總是一次獲得所需要的全部鎖,要麼全部滿足,要麼全等待;
- InnoDB除了單SQL事務,鎖是逐步獲得的,因此可能出現死鎖。一般InnoDB能自動檢測死鎖,並使一個較簡單的事務回退並釋放鎖,另一個事務獲得鎖,繼續完成事務。
Linux公社的RSS地址 :ofollow,noindex" target="_blank">https://www.linuxidc.com/rssFeed.aspx
本文永久更新連結地址:https://www.linuxidc.com/Linux/2018-10/155054.htm