MySQL—事務和鎖

MySQL鎖

和其他數據庫相比,MySQL的鎖機制比較假單,不同的引擎支持不同的鎖機制。MyISAM和MEMORY使用表級鎖,BDB使用頁面鎖和表級鎖;InnoDB默認支持行級鎖,也支持表級鎖。

  • 表級鎖:開銷小,加鎖塊,不會出現死鎖;鎖顆粒度大,容易出現鎖衝突,併發級數小。
  • 行級鎖:開銷大,加鎖慢,會出現死鎖;鎖顆粒度小,不容易出現鎖衝突,併發級數大。
  • 頁面鎖:介於表級鎖和行級鎖之間。

MyISAM表鎖

MyISAM表鎖有兩中,一個是都鎖,一個是寫鎖,兼容性如下:

模式 讀鎖 寫鎖
讀鎖 兼容 不兼容
寫鎖 不兼容 不兼容

可見MyISAM對錶的讀取不會造成其他用戶對錶的讀取,但會阻塞其他用的寫。因此,除了讀-讀操作,其他對數據的操作都是串行的。

MyISAM在執行SELECT前,會自動的給涉及到的所有表加讀鎖,使用跟新操作前都會自動的加寫鎖,這些過程不需要用戶干預。

併發插入:在一定程度上,MyISAM也支持查詢和插入操作的併發進行。MyISAM中有一個系統變量concurrent_insert,專門控制併發插入,可以取值爲0、1、2。

  • 設置爲0時:表示不允許併發插入。
  • 設置爲1時:表示如果表中沒有空洞(中間沒有被刪除的行),MyISAM允許在表尾插入記錄。
  • 設置爲2時:表示無論是否有空洞,都允許在表尾插入數據。
    可以使用MyISAM的併發插入特性來解決應用中對同一個表查詢和插入的鎖競爭。

MyISAM鎖調度

對於同時存在讀寫操作競爭鎖的時候,默認情況下MyISAM中寫進程會先獲得鎖,也就是說MyISAM認爲寫請求比讀請求更加重要。這也就解釋了MyISAM不適合有大量跟新操作的原因,因爲大量的更新操作會使得讀線程很難獲得鎖,從而造成永遠的阻塞。我們通過一些設置來改變這種情況。

  1. 指定啓動參數low-priority-updates,使得讀請求有優先的權利。
  2. 執行SET LOW_PRIORITY_UPDATES=1.
  3. 指定INSERT、UPDATE、DELETE語句的LOW_PRIORITY屬性。

InnoDB事務

InnoDB是支持事務的,這是和MyISAM最大的不同。

事務以及ACID屬性:

  • 原子性Atomicity:事務是原子操作單元,對數據的修改要麼全都正確執行,要麼全都不執行。

  • 一致性Consistent:事務開始前和事務結束後,數據庫的完整性約束沒有被破壞。

  • 隔離性Isolation:事務的執行是相互獨立的,它們不會相互干擾,一個事務不會看到另一個正在運行過程中的事務的數據。

  • 持久性Durable:事務結束後,事務的結果必須是永久保存的。


事務併發處理帶來的問題

  • 更新丟失:當數據庫沒有加任何鎖操作的情況下,有兩個併發執行的事務,更新同一行數據,那麼有可能一個事務會把另一個事務的更新覆蓋掉。

  • 髒讀:一個事務讀到另一個尚未提交的事務中的數據。

  • 不可重複讀書:同一個事務中在不同的時間讀取同一個數據,結果不同。

  • 幻讀:一個事務按照相同查詢條件查詢之前檢索過的數據,卻發現多了滿足查詢條件的新數據。

事務隔離級別

1.Read uncommitted未提交讀:從名字可以知道,在該級別下,一個事務對一行數據修改的過程中,允許另一個事務對該行數據進行修改。
2. Read committed已提交讀:在該級別下,一個事務對一行數據修改的過程中,不允許另一個事務對該行未提交數據進行修改,但允許另一個事務對該行數據讀。 雖然解決了髒讀,但是會出現不可重複讀和幻讀。
3. Repeatable read可重複讀:在該級別下,讀事務禁止寫事務,但允許讀事務,因此解決了不可重複讀,且寫事務禁止其他一切事務。
4. Serializable 序列化:最高的隔離級別,該級別下,讀事務禁止寫事務,但允許讀事務,且寫事務禁止其他一切事務。

數據庫實現事務隔離機制的方法主要有兩種

  • 讀取數據之前爲數據加上鎖,防止其他事務對數據的修改。

  • 不加任何鎖,通過一定機制生成一個數據請求時間點的一致數據快照,用其提供一定級別的一致性讀取,就好像數據庫可以提供多版本的數據一樣。於是這種技術就叫數據多版本併發控制簡稱MVCC,也稱爲多版本數據庫。


InnoDB鎖

InnoDB中默認的是行級鎖,和MyISAM一樣有兩種鎖:

  • 共享鎖(S):允許多個事務讀取一行,阻止其他事務獲得相同數據級的排他鎖。
  • 排他鎖(X):允許獲得排他鎖的事務更新數據,阻止其他事務取得相同數據的排他鎖和共享鎖。
    兼容情況如下:
鎖模式 X S
X 衝突 衝突
S 衝突 兼容

對於UPDATE、INSERT、DELETE語句,InnoDB會自動的加上X鎖;一般的SELECT語句不會加鎖,除非顯示的給記錄集加上鎖。如果取得數據的共享鎖以後,還要進行更新,就會造成死鎖(後面會講到);因此如果要對數據要進行更新就直接獲得排它鎖即可。

InnoDB行鎖實現的方式

InnoDB行鎖是通過給索引上的索引項加鎖來實現的,從而間接對一行數據集上鎖,如果沒有索引,那麼就會通過隱藏的聚集索引來對記錄上鎖。行鎖有三種:

  • Record lock:對索引加鎖。
  • Gap lock:對索引的間隙加鎖。
  • Next-key lock:上面兩者的組合。

這種加鎖機制表名,如果不通過索引條件查詢數據,那麼InnoDB將會對錶中所有數據記錄加鎖,這就和表鎖的實際效果一樣了。因此需要注意如下情況:

  1. 不通過索引查詢數據,InnoDB會鎖定整個表記錄。
  2. 如果不同事務使用相同的索引鍵則會出現鎖衝突。
  3. 表有多個索引時候,不同事務使用不同的索引鎖定不同的行,無論使用哪種索引,都會鎖定數據。
    事務1查詢 select * from table where id = 1 for update;
    結果id :1 name :1
    id:1 name:4
    事務2查詢 select * from table where name= 4 for update ;
    會造成阻塞,知道事務1釋放鎖。
  4. 即便是在條件使用了索引字段,MySQL也不一定會使用索引(上一篇講索引時指出了原因),因此分析鎖衝突是需要確認是否真正使用了索引。

NEXT-KEY 鎖

對於鍵值在條件範圍以內但並不存在記錄的叫做“間隙”,InnoDB會對這個間隙加鎖,這就是所謂的Next-Key鎖。

select * from table where id>100 for update;

對於上述語句,InnoDB不僅會對id位爲101記錄上鎖,而且還會對大於101的記錄上鎖,雖然這些記錄並不存在。InnoDB使用NEXT-KEY 鎖的好處就是防止了幻讀的出現,以滿足相關隔離級別的要求。雖然InnoDB默認的隔離級別是RR(可重複讀),但是防止了幻讀的出現,實際上達到了Serializable級別。

很顯然,這也會帶來一個問題:在使用範圍查找時,InnoDB這種加鎖機制會造成嚴重的鎖等待,所以在實際開發中需要優化業務邏輯,避免出現範圍條件。

關於死鎖

當兩個事務都需要獲得對方的排他鎖時,會出現循環等待,也就是“死鎖”。對於InnoDB,死鎖都能自動檢測到,並使得一個事務回滾並釋放鎖。
避免死鎖的辦法:
1.設置合適的鎖等待時間,一旦超過這個時間閾,就會自動回滾並釋放鎖。
2.儘量約定以相同的順序來訪問表,這就會大大降低產生死鎖的機會。
3.在事務中,申請足夠級別的排他鎖,如:需要更新數據就直接申請排它鎖。
4.死鎖很難避免,因此需要在程序設計中捕獲並處理死鎖異常是一個好的習慣。

總結:

  1. MyISAM鎖的特點?以及鎖的調度機制?
  2. InnoDB的行鎖是如何實現的?
  3. InnoDB爲什麼在RR級別下解決了幻讀的出現?
  4. InnoDB如何減少死鎖的發生?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章