1. 程式人生 > >MySQL鎖問題 - MyISAM與InnoDB的鎖機制

MySQL鎖問題 - MyISAM與InnoDB的鎖機制

目錄

一、MySQL鎖概述

MySQL的鎖機制相對於其他資料庫而言比較簡單,其最顯著的特點是不同的儲存引擎支援不同的鎖機制

1、表級鎖和行級鎖的特點

鎖型別 開銷 加鎖速度 死鎖 粒度 發生衝突概率 併發度
表級鎖 最高 最低
行級鎖 最小 最低 最高

2、表級鎖和行級鎖的適用場景

僅從鎖的角度來講:

鎖型別 適合場景 例項
表級鎖 以查詢為主,只有少量按索引更新資料的應用 web
行級鎖 有大量按索引條件併發更新少量不同資料,同時又有併發查詢的用用 OLTP

二、MyISAM表鎖

MyISAM只支援表鎖

1、查詢表級鎖爭用情況

可以通過檢查table_locks_waitedtable_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使該連結發出的更新請求優先順序降低
  • 通過指定INSERTUPDATEDELETE語句的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=INNODBshow 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鎖

6、恢復和複製的需要,對InnoDB鎖機制的影響

7、InnoDB在不同隔離級別下的一致性讀及鎖的差異

8、什麼時候使用表鎖

9、關於死鎖

四、總結