mysql 鎖

引言:

鎖是計算機協調多個進程或者多個線程之間併發訪問同一資源的機制。在數據庫系統中,除了傳統的計算機資源(CPU、RAM、I/O)的爭用之外,數據也是一種供許多用戶共享的資源。如何保證數據併發訪問的一致性和有效性是全部數據庫系統須要考慮的問題。鎖衝突也是影響數據庫併發訪問性能的一個重要因素。從這個角度來說,鎖對於數據庫尤爲重要,也更加複雜。

對於mysql來講,不一樣的引擎鎖的實現方式不同,因此須要根據不一樣數據庫引擎來進行討論。mysql

1.MyISAM

鎖分類

MyISAM 引擎的表鎖有2種模式,讀鎖寫鎖sql

鎖之間的關係

  • 讀鎖之間:並行,不阻塞
  • 讀寫鎖之間:串行阻塞
  • 寫鎖之間:串行阻塞

加鎖時機

MyISAM在執行查詢語句(SELECT)前,會自動給涉及的全部表加讀鎖,在執行更新操做(UPDATE、DELETE、INSERT等)前,會自動給涉及的表加寫鎖,這個過程並不須要用戶干預,所以用戶通常不須要直接用LOCK TABLE命令給MyISAM表顯式加鎖。數據庫

MyISAM存儲引擎的讀和寫鎖是互斥,讀操做是串行的。那麼,一個進程請求某個MyISAM表的讀鎖,同時另外一個進程也請求同一表的寫鎖,MySQL如何處理呢?答案是寫進程先得到鎖。不只如此,即便讀進程先請求先到鎖等待隊列,寫請求後到,寫鎖也會插到讀請求以前!這是由於MySQL認爲寫請求通常比讀請求重要。這也正是MyISAM表不太適合於有大量更新操做和查詢操做應用的緣由,由於,大量的更新操做會形成查詢操做很難得到讀鎖,從而可能永遠阻塞。這種狀況有時可能會變得很是糟糕!幸虧咱們能夠經過一些設置來調節MyISAM的調度行爲。經過指定啓動參數low-priority-updates,使MyISAM引擎默認給予讀請求以優先的權利。session

2.InnoDB

InnoDB與MyISAM的最大不一樣有兩點:一是支持事務(TRANSACTION);二是採用了行級鎖。行級鎖和表級鎖原本就有許多不一樣之處,另外,事務的引入也帶來了一些新問題。數據結構

1.事務及其ACID屬性

​ 事務是由一組SQL語句組成的邏輯處理單元,事務具備4屬性,一般稱爲事務的ACID屬性。併發

  • 原性性(Actomicity):事務是一個原子操做單元,其對數據的修改,要麼全都執行,要麼全都不執行。
  • 一致性(Consistent):在事務開始和完成時,數據都必須保持一致狀態。這意味着全部相關的數據規則都必須應用於事務的修改,以操持完整性;事務結束時,全部的內部數據結構(如B樹索引或雙向鏈表)也都必須是正確的。
  • 隔離性(Isolation):數據庫系統提供必定的隔離機制,保證事務在不受外部併發操做影響的「獨立」環境執行。這意味着事務處理過程當中的中間狀態對外部是不可見的,反之亦然。
  • 持久性(Durable):事務完成以後,它對於數據的修改是永久性的,即便出現系統故障也可以保持。

2.併發事務帶來的問題

​ 相對於串行處理來講,併發事務處理能大大增長數據庫資源的利用率,提升數據庫系統的事務吞吐量,從而能夠支持能夠支持更多的用戶。但併發事務處理也會帶來一些問題,主要包括如下幾種狀況。性能

  • 更新丟失(Lost Update):當兩個或多個事務選擇同一行,而後基於最初選定的值更新該行時,因爲每一個事務都不知道其餘事務的存在,就會發生丟失更新問題——最後的更新覆蓋了其餘事務所作的更新。例如,兩個編輯人員製做了同一文檔的電子副本。每一個編輯人員獨立地更改其副本,而後保存更改後的副本,這樣就覆蓋了原始文檔。最後保存其更改保存其更改副本的編輯人員覆蓋另外一個編輯人員所作的修改。若是在一個編輯人員完成並提交事務以前,另外一個編輯人員不能訪問同一文件,則可避免此問題
  • 髒讀(Dirty Reads):一個事務正在對一條記錄作修改,在這個事務並提交前,這條記錄的數據就處於不一致狀態;這時,另外一個事務也來讀取同一條記錄,若是不加控制,第二個事務讀取了這些「髒」的數據,並據此作進一步的處理,就會產生未提交的數據依賴關係。這種現象被形象地叫作「髒讀」。
  • 不可重複讀(Non-Repeatable Reads):一個事務在讀取某些數據已經發生了改變、或某些記錄已經被刪除了!這種現象叫作「不可重複讀」。
  • 幻讀(Phantom Reads):一個事務按相同的查詢條件從新讀取之前檢索過的數據,卻發現其餘事務插入了知足其查詢條件的新數據,這種現象就稱爲「幻讀」。

3.事務隔離級別

在併發事務處理帶來的問題中,「更新丟失」一般應該是徹底避免的。但防止更新丟失,並不能單靠數據庫事務控制器來解決,須要應用程序對要更新的數據加必要的鎖來解決,所以,防止更新丟失應該是應用的責任。優化

「髒讀」、「不可重複讀」和「幻讀」,其實都是數據庫讀一致性問題,必須由數據庫提供必定的事務隔離機制來解決。數據庫實現事務隔離的方式,基本能夠分爲如下兩種。線程

一種是在讀取數據前,對其加鎖,阻止其餘事務對數據進行修改。設計

另外一種是不用加任何鎖,經過必定機制生成一個數據請求時間點的一致性數據快照(Snapshot),並用這個快照來提供必定級別(語句級或事務級)的一致性讀取。從用戶的角度,好像是數據庫能夠提供同一數據的多個版本,所以,這種技術叫作數據多版本併發控制(MultiVersion Concurrency Control,簡稱MVCC或MCC),也常常稱爲多版本數據庫。

數據庫的事務隔離級別越嚴格,併發反作用越小,但付出的代價也就越大,由於事務隔離實質上就是使事務在必定程度上「串行化」進行,這顯然與「併發」是矛盾的,同時,不一樣的應用對讀一致性和事務隔離程度的要求也是不一樣的,好比許多應用對「不可重複讀」和「幻讀」並不敏感,可能更關心數據併發訪問的能力。

爲了解決「隔離」與「併發」的矛盾,ISO/ANSI SQL92定義了4個事務隔離級別,每一個級別的隔離程度不一樣,容許出現的反作用也不一樣,應用能夠根據本身業務邏輯要求,經過選擇不一樣的隔離級別來平衡"隔離"與"併發"的矛盾

隔離級別 讀數據一致性 髒讀 不可重複讀 幻讀
未提交讀(Read uncommitted) 最低級別,只能保證不讀取物理上損壞的數據
已提交度(Read committed) 語句級
可重複讀(Repeatable read) 事務級
可序列化(Serializable) 最高級別,事務級

4.鎖分類:

InnoDB鎖包括了共享鎖和排他鎖,同時爲了容許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖。

  • 共享鎖(s):容許一個事務去讀一行,阻止其餘事務得到相同數據集的排他鎖。
  • 排他鎖(X):容許獲取排他鎖的事務更新數據,阻止其餘事務取得相同的數據集共享讀鎖和排他寫鎖。
  • 意向共享鎖(IS):事務打算給數據行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖。
  • 意向排他鎖(IX):事務打算給數據行加排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖。
當前鎖模式/是否兼容 X IX S IS
X(排他鎖) 衝突 衝突 衝突 衝突
S(共享鎖) 衝突 衝突 兼容 兼容
IX(意向排他鎖) 衝突 兼容 衝突 兼容
IS(意向共享鎖) 衝突 兼容 兼容 兼容

4.1.間隙鎖(Next-Key鎖):影響併發insert的鎖

鍵值在條件範圍內但並不存在的記錄,叫作「間隙(GAP)」,InnoDB也會對這個「間隙」加鎖,這種鎖機制就是所謂的間隙鎖 (Next-Key鎖)。舉例來講,假若有以下數據表:

Id Name Age
1 p1 10
4 p2 18
8 p3 12

這時候執行以下的sql語句:

select * from demo where id>1 for update;

這時候在數據庫中會對id=4,8的記錄添加行鎖。同時會添加以下間隙鎖:(2,4], (4,8],(8, +suprenum],這種狀況下若是要在數據庫中insert id在間隙鎖之間的記錄是會被阻塞的。

4.1.1.範圍查詢間隙鎖

當咱們用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的 索引項加鎖。
舉例來講有表demo:

Id Name Age
1 p1 10
2 p2 18
3 p3 12

開啓第一個命令執行窗口執行for update操做:

mysql> set autocommit = 0;
Query OK, 0 rows affected (0.01 秒)

mysql> select * from demo where id>2 for update;
+----+------+------+
| id | name | age  |
+----+------+------+
| 3  | p3   | 12   |
| 4  | p4   | 11   |
+----+------+------+
2 行於數據集 (0.02 秒)

mysql>

這是一個範圍條件的檢索,InnoDB不只會對符合條件的empid值爲3,4的記錄加鎖,也會對id大於3(這些記錄並不存在)的「間隙」加鎖。

開啓第二個命令窗口執行插入數據操做:

mysql> set autocommit = 0;
Query OK, 0 rows affected (0.01 秒)

mysql> insert into demo(name,age) values('p5', 22);
Lock wait timeout exceeded; try restarting transaction

能夠看到這條sql執行會被阻塞,由於大於3的不存在的記錄也被加上了間隙鎖了,因此在執行插入時就執行不下去了,由於新增記錄的id是大於3的。

4.1.2.等值記錄不存在致使間隙鎖

除了範圍查詢會使用間隙鎖以外,對於等值查詢而不存在的記錄也會使用間隙鎖,一樣打開第一個命令窗口,去更新一條不存在的記錄。

假如當前數據庫記錄以下:

Id Name Age
1 p1 10
2 p2 18
6 p3 12

開啓第一個命令執行窗口執行for update操做:

mysql> set autocommit = 0;
Query OK, 0 rows affected (0.01 秒)

mysql> update demo set name='pp' where id=3;
Query OK, 0 rows affected (0.00 秒)

這時候去另外一個命令窗口新增一條記錄,能夠看到這條記錄一直阻塞到超時也沒有執行,這是由於若是一個等值的記錄不存在的狀況下,mysql會使用間隙鎖鎖住它臨近的前面和後面2條記錄之間的全部記錄。對於id=3的記錄來講不存在,因此他會鎖住id(2-6]的全部記錄。

mysql> set autocommit = 0;
Query OK, 0 rows affected (0.01 秒)

mysql> insert into demo(id,name, age) values(4,'p5', 88);
Lock wait timeout exceeded; try restarting transaction

InnoDB使用間隙鎖的目的,一方面是爲了防止幻讀,以知足相關隔離級別的要求,對於上面的例子,要是不使 用間隙鎖,若是其餘事務插入了empid大於100的任何記錄,那麼本事務若是再次執行上述語句,就會發生幻讀;另一方面,是爲了知足其恢復和複製的須要。有關其恢復和複製對鎖機制的影響,以及不一樣隔離級別下InnoDB使用間隙鎖的狀況,在後續的章節中會作進一步介紹。

很顯然,在使用範圍條件檢索並鎖定記錄時,InnoDB這種加鎖機制會阻塞符合條件範圍內鍵值的併發插入,這每每會形成嚴重的鎖等待。所以,在實際應用開發中,尤爲是併發插入比較多的應用,咱們要儘可能優化業務邏輯,儘可能使用相等條件來訪問更新數據,避免使用範圍條件,對於刪除。

5.加鎖時機:

在MVCC併發控制中,讀操做能夠分爲2類。快照讀當前讀,其中快照讀讀取的是可見版本,不加鎖;而當前讀讀取的是最新版本,而且當前讀返回的記錄都會加鎖,保證其餘併發事務不能修改該記錄。那麼MVCC中那些語句會加鎖,那些不加鎖呢?

快照讀簡單的select屬於快照讀,不須要加鎖。

當前讀:特殊的select操做(select for update),insert/update/delete屬於當前讀,都須要加鎖。

6.何時使用表鎖

​ 對於InnoDB表,在絕大部分狀況下都應該使用行級鎖,由於事務和行鎖每每是咱們之因此選擇InnoDB表的理由。但在個另特殊事務中,也能夠考慮使用表級鎖。

  • 第一種狀況是:事務須要更新大部分或所有數據,表又比較大,若是使用默認的行鎖,不只這個事務執行效率低,並且可能形成其餘事務長時間鎖等待和鎖衝突,這種狀況下能夠考慮使用表鎖來提升該事務的執行速度。
  • 第二種狀況是:事務涉及多個表,比較複雜,極可能引發死鎖形成大量事務回滾。這種狀況也能夠考慮一次性鎖定事務涉及的表,從而避免死鎖、減小數據庫因事務回滾帶來的開銷。

​ 固然,應用中這兩種事務不能太多,不然,就應該考慮使用MyISAM表。

​ 在InnoDB下 ,使用表鎖要注意如下兩點。

​ (1)使用LOCK TALBES雖然能夠給InnoDB加表級鎖,但必須說明的是,表鎖不是由InnoDB存儲引擎層管理的,而是由其上一層MySQL Server負責的,僅當autocommit=0、innodb_table_lock=1(默認設置)時,InnoDB層才能知道MySQL加的表鎖,MySQL Server才能感知InnoDB加的行鎖,這種狀況下,InnoDB才能自動識別涉及表級鎖的死鎖;不然,InnoDB將沒法自動檢測並處理這種死鎖。

​ (2)在用LOCAK TABLES對InnoDB鎖時要注意,要將AUTOCOMMIT設爲0,不然MySQL不會給表加鎖;事務結束前,不要用UNLOCAK TABLES釋放表鎖,由於UNLOCK TABLES會隱含地提交事務;COMMIT或ROLLBACK產不能釋放用LOCAK TABLES加的表級鎖,必須用UNLOCK TABLES釋放表鎖,正確的方式見以下語句。

注意:

InnoDB 行鎖是經過給索引上的索引項加鎖來實現的,只有經過索引條件檢索數據,InnoDB 才使用行級鎖,不然,InnoDB將使用表鎖
因爲 MySQL 行鎖是針對索引加的鎖,不是針對記錄加的鎖,因此雖然是訪問不一樣行的記錄,可是若是是使用相同的索引鍵,是會出現鎖衝突的,設計的時候要注意這一點

當表有多個索引的時候,不一樣的事務可使用不一樣的索引鎖定不一樣的行,另外,不管是使用主鍵索引、惟一索引或普通索引,InnoDB都會使用行鎖來對數據加鎖

即使在條件中使用了索引字段,可是否使用索引來檢索數據是由 MySQL 經過判斷不一樣執行計劃的代價來決定的,若是 MySQL 認爲全表掃描效率更高,好比對一些很小的表,它就不會使用索引,這種狀況下 InnoDB 將使用表鎖,而不是行鎖。所以,在分析鎖衝突時,別忘了檢查 SQL 的執行計劃,以確認是否真正使用了索引

檢索值的數據類型與索引字段不一樣,雖然 MySQL 可以進行數據類型轉換,但卻不會使用索引,從而致使 InnoDB 使用表鎖。經過用explain 檢查兩條SQL的執行計劃

3.死鎖

1.死鎖的產生緣由

所謂死鎖是2個或者2個以上的併發進程在執行過程當中因爭奪資源而相互等待的現象,若無外力做用它們都將沒法繼續推動下去,此時稱系統處於死鎖狀態或系統產生了死鎖。這些永遠相互等待的進程稱爲死鎖進程。表級鎖不會產生死鎖,因此解決死鎖主要仍是針對於InnoDB。

死鎖的關鍵在於:兩個(或以上)的Session加鎖的順序不一致

那麼對應的解決死鎖問題的關鍵就是:讓不一樣的session加鎖有次序

2.常見的死鎖案例:

1.不一樣表相同記錄行鎖衝突

這種狀況很好理解,事務A和事務B操做兩張表,但出現循環等待鎖狀況。

事務A 事務B
begin begin
delete from t1 where id=1
update t2 set name='test' where id=2
update t2 set name='test' where id=2
delete from t1 where id=1
2.相同表記錄行鎖衝突

這種狀況比較常見,以前遇到兩個job在執行數據批量更新時,jobA處理的的id列表爲[1,2,3,4],而job處理的id列表爲[8,9,10,4,2],這樣就形成了死鎖。

事務A 事務B
begin begin
update t2 set name='test' where id=1
update t2 set name='test' where id=2
update t2 set name='test' where id=2
update t2 set name='test' where id=1
3.不一樣索引鎖衝突

這種狀況比較隱晦,事務A在執行時,除了在二級索引加鎖外,還會在聚簇索引上加鎖,在聚簇索引上加鎖的順序是[1,4,2,3,5],而事務B執行時,只在聚簇索引上加鎖,加鎖順序是[2,3,4,5],這樣就形成了死鎖的可能性。

事務A 事務B
update msg set message='訂單' where token>'abc' delete from msg where id>1
4. gap鎖衝突

innodb在RR級別下,以下的狀況也會產生死鎖,比較隱晦。不清楚的同窗能夠自行根據上節的gap鎖原理分析下。

事務A 事務B
begin begin
update msg set message='訂單' where token='asd'
update msg set message='訂單' where token='aaa'
insert into msg values(null, 'aad', 'hello');
commit insert into msg values(null, 'bsa', 'hello');
commit

3.如何儘量避免死鎖

  1. 固定的順序訪問表和行。好比對第2節兩個job批量更新的情形,簡單方法是對id列表先排序,後執行,這樣就避免了交叉等待鎖的情形;又好比對於3.1節的情形,將兩個事務的sql順序調整爲一致,也能避免死鎖。
  2. 大事務拆小。大事務更傾向於死鎖,若是業務容許,將大事務拆小。
  3. 在同一個事務中,儘量作到一次鎖定所須要的全部資源,減小死鎖機率。
  4. 下降隔離級別。若是業務容許,將隔離級別調低也是較好的選擇,好比將隔離級別從RR調整爲RC,能夠避免掉不少由於gap鎖形成的死鎖。
  5. 爲表添加合理的索引。能夠看到若是不走索引將會爲表的每一行記錄添加上鎖,死鎖的機率大大增大。

總結

MyISAM注意點

​ (1)共享讀鎖(S)之間是兼容的,但共享讀鎖(S)和排他寫鎖(X)之間,以及排他寫鎖之間(X)是互斥的,也就是說讀和寫是串行的。

​ (2)在必定條件下,MyISAM容許查詢和插入併發執行,咱們能夠利用這一點來解決應用中對同一表和插入的鎖爭用問題。

​ (3)MyISAM默認的鎖調度機制是寫優先,這並不必定適合全部應用,用戶能夠經過設置LOW_PRIPORITY_UPDATES參數,或在INSERT、UPDATE、DELETE語句中指定LOW_PRIORITY選項來調節讀寫鎖的爭用。

​ (4)因爲表鎖的鎖定粒度大,讀寫之間又是串行的,所以,若是更新操做較多,MyISAM表可能會出現嚴重的鎖等待,能夠考慮採用InnoDB表來減小鎖衝突。

InnoDB 注意點

​ (1)InnoDB的行銷是基於索引實現的,若是不經過索引訪問數據,InnoDB會使用表鎖。

​ (2)InnoDB間隙鎖機制,以及InnoDB使用間隙鎖的緣由。

​ (3)在不一樣的隔離級別下,InnoDB的鎖機制和一致性讀策略不一樣。

​ (4)MySQL的恢復和複製對InnoDB鎖機制和一致性讀策略也有較大影響。

​ (5)鎖衝突甚至死鎖很難徹底避免。

​ 在瞭解InnoDB的鎖特性後,用戶能夠經過設計和SQL調整等措施減小鎖衝突和死鎖,包括:

  • 儘可能使用較低的隔離級別
  • 精心設計索引,並儘可能使用索引訪問數據,使加鎖更精確,從而減小鎖衝突的機會。
  • 選擇合理的事務大小,小事務發生鎖衝突的概率也更小。
  • 給記錄集顯示加鎖時,最好一次性請求足夠級別的鎖。好比要修改數據的話,最好直接申請排他鎖,而不是先申請共享鎖,修改時再請求排他鎖,這樣容易產生死鎖。
  • 不一樣的程序訪問一組表時,應儘可能約定以相同的順序訪問各表,對一個表而言,儘量以固定的順序存取表中的行。這樣能夠大減小死鎖的機會。
  • 儘可能用相等條件訪問數據,這樣能夠避免間隙鎖對併發插入的影響。
  • 不要申請超過實際須要的鎖級別;除非必須,查詢時不要顯示加鎖。
  • 對於一些特定的事務,可使用表鎖來提升處理速度或減小死鎖的可能。
  • 間隙鎖是針對insert而致使阻塞的鎖,實際開發中應該儘可能避免間隙鎖的發生,對於範圍查詢以及等值查詢的修改,儘可能先去查詢數據是否存在,存在再去執行更新和刪除操做,不然可能會產生死鎖。

    鎖、事務、隔離級別、acid之間的關係:

    鎖是解決併發的機制,事務是對一組sql處理單元的統稱,ACID是事務的4個屬性,隔離級別是對事務之間隔離程度的一種描述。

相關文章
相關標籤/搜索