鎖類型/引擎 |
行鎖 |
表鎖 |
頁鎖 |
MyISAM |
|
有 |
|
InnoDB |
有 |
有 |
|
BDB(被InnoDB取代) |
|
有 |
有 |
鎖的分類
- 表鎖:開銷小,加鎖快,不會死鎖,粒度大,沖突率高,並發低。
- 行鎖:開銷大,加鎖慢,會死鎖,粒度小,沖突率低,並發高。
- 頁鎖:處於表鎖和行鎖之間,會死鎖。
鎖的適用場景
- 表鎖:更適用於查詢為主,按少量索引條件更新。
- 行鎖:更適用於大量按索引並發更新少量不同數據,同時又有並發查詢。
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、不加鎖,生成快照,多版本並發控制 |
不可重復讀 |
一個事務多次讀取同一數據發現被改變/刪除 |
同上 |
幻讀 |
一個事務按先前的條件查詢,發現其他事務插入了滿足條件的新數據 |
同上 |
注:
事務隔離級別越高,並發副作用越小,代價越高,因為事務隔離從某種程度上說使得事務串行化。
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能自動檢測死鎖,並使一個較簡單的事務回退並釋放鎖,另一個事務獲得鎖,繼續完成事務。