1. 程式人生 > 實用技巧 >MySQL中的鎖

MySQL中的鎖

MySQL常用儲存引擎的鎖機制

  • MyISAM和MEMORY採用表級鎖(table-level locking)
  • BDB採用頁面鎖(page-level locking)或表級鎖,預設為頁面鎖
  • InnoDB支援行級鎖(row-level locking)和表級鎖,預設為行級鎖

InnoDB和MyISAM的最大不同點有兩個

  1. InnoDB支援事務(transaction);
  2. 預設採用行級鎖。加鎖可以保證事務的一致性,有鎖的地方,就有事務;

鎖的劃分

  • 按鎖的粒度劃分,可分為表級鎖、行級鎖、頁級鎖(mysql)
  • 按鎖級別劃分,可分為共享鎖、排他鎖
  • 按加鎖方式劃分,可分為自動鎖、顯示鎖
  • 按使用方式劃分,可分為樂觀鎖、悲觀鎖

按鎖的粒度劃分

行級鎖

行級鎖是MySQL中粒度最小的一種鎖,只對當前操作的行進行加鎖。

特點:粒度最小,行級鎖分為 共享鎖和排它鎖(下面會分析這兩種鎖)

優點:資料庫鎖衝突最小

缺點:因為粒度最小所以開銷最大,加鎖速度最慢。

加鎖的方式:自動加鎖。對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及資料集加排他鎖;對於普通SELECT語句,InnoDB不會加任何鎖;當然我們也可以顯示的加鎖:
共享鎖:select * from tableName where ... + lock in share more
排他鎖:select * from tableName where ... + for update

行鎖優化

  1. 儘可能讓所有資料檢索都通過索引來完成,避免無索引行或索引失效導致行鎖升級為表鎖。
  2. 儘可能避免間隙鎖帶來的效能下降,減少或使用合理的檢索範圍。
  3. 儘可能減少事務的粒度,比如控制事務大小,而從減少鎖定資源量和時間長度,從而減少鎖的競爭等,提供效能。
  4. 儘可能低級別事務隔離,隔離級別越高,併發的處理能力越低。

注意:InnoDB的行鎖是針對索引加的鎖,不是針對記錄加的鎖。並且該索引不能失效,否則都會從行鎖升級為表鎖。

表級鎖

表級鎖是MySQL中粒度最大的一種鎖,對當前操作的整張表加鎖。

特點:粒度最大,表級鎖分為 共享鎖和排它鎖(下面會分析這兩種鎖)

優點:因為粒度最大所以開銷最小,加鎖速度最快。

缺點:發生鎖衝突的概率最高,並法度最低。

加鎖的方式:自動加鎖。查詢操作(SELECT),會自動給涉及的所有表加讀鎖,更新操作(UPDATE、DELETE、INSERT),會自動給涉及的表加寫鎖。也可以顯示加鎖:
共享讀鎖:lock table tableName read;
獨佔寫鎖:lock table tableName write;

批量解鎖:unlock tables;

什麼場景下用表鎖

InnoDB預設採用行鎖,在未使用索引欄位查詢時升級為表鎖。MySQL這樣設計並不是給你挖坑。它有自己的設計目的。
即便你在條件中使用了索引欄位,MySQL會根據自身的執行計劃,考慮是否使用索引(所以explain命令中會有possible_key 和 key)。如果MySQL認為全表掃描效率更高,它就不會使用索引,這種情況下InnoDB將使用表鎖,而不是行鎖。因此,在分析鎖衝突時,別忘了檢查SQL的執行計劃,以確認是否真正使用了索引。
第一種情況:全表更新。事務需要更新大部分或全部資料,且表又比較大。若使用行鎖,會導致事務執行效率低,從而可能造成其他事務長時間鎖等待和更多的鎖衝突。
第二種情況:多表查詢。事務涉及多個表,比較複雜的關聯查詢,很可能引起死鎖,造成大量事務回滾。這種情況若能一次性鎖定事務涉及的表,從而可以避免死鎖、減少資料庫因事務回滾帶來的開銷。

頁級鎖

頁級鎖是MySQL中鎖粒度結餘行級鎖和表級鎖之間的一種鎖。

特點:以上兩種的折衷。

總結

  • 鎖的粒度越大,加鎖速度越快,越不容易出現死鎖,但鎖衝突的概率會上升,併發會下降。
  • InnoDB 支援表鎖和行鎖,使用索引作為檢索條件修改資料時採用行鎖,否則採用表鎖。

按鎖的級別劃分

共享鎖(Share Lock)

共享鎖又稱讀鎖,是讀取操作建立的鎖。其他使用者可以併發讀取資料,但任何事務都不能對資料進行修改(獲取資料上的排他鎖),直到已釋放所有共享鎖。
如果事務T對資料A加上共享鎖後,則其他事務只能對A再加共享鎖,不能加排他鎖。獲准共享鎖的事務只能讀資料,不能修改資料。
用法

SELECT ... LOCK IN SHARE MODE;

在查詢語句後面增加LOCK IN SHARE MODE,Mysql會對查詢結果中的每行都加共享鎖,當沒有其他執行緒對查詢結果集中的任何一行使用排他鎖時,可以成功申請共享鎖,否則會被阻塞。其他執行緒也可以讀取使用了共享鎖的表,而且這些執行緒讀取的是同一個版本的資料。

排他鎖(eXclusive Lock)

排他鎖又稱寫鎖,如果事務T對資料A加上排他鎖後,則其他事務不能再對A加任任何型別的封鎖。獲准排他鎖的事務既能讀資料,又能修改資料。
用法

SELECT ... FOR UPDATE;

在查詢語句後面增加FOR UPDATE,Mysql會對查詢結果中的每行都加排他鎖,當沒有其他執行緒對查詢結果集中的任何一行使用排他鎖時,可以成功申請排他鎖,否則會被阻塞。

使用方法總結:

共享鎖:

SELECT ... LOCK IN SHARE MODE;

排他鎖:

SELECT ... FOR UPDATE;

使用方式劃分

樂觀鎖(Optimistic Lock)

樂觀鎖,也叫樂觀併發控制,它假設多使用者併發的事務在處理時不會彼此互相影響,各事務能夠在不產生鎖的情況下處理各自影響的那部分資料。在提交資料更新之前,每個事務會先檢查在該事務讀取資料後,有沒有其他事務又修改了該資料。如果其他事務有更新的話,那麼當前正在提交的事務會進行回滾。

樂觀鎖的特點先進行業務操作,不到萬不得已不去拿鎖。即“樂觀”的認為拿鎖多半是會成功的,因此在進行完業務操作需要實際更新資料的最後一步再去拿一下鎖就好。

樂觀鎖在資料庫上的實現完全是邏輯的,不需要資料庫提供特殊的支援。一般的做法是在需要鎖的資料上增加一個版本號,或者時間戳,然後按照如下方式實現:

樂觀鎖(給表加一個版本號欄位)這個並不是樂觀鎖的定義,給表加版本號,是資料庫實現樂觀鎖的一種方式。

1. SELECT data AS old_data, version ASold_version FROM …;

2. 根據獲取的資料進行業務操作,得到new_data和new_version

3. UPDATE SET data = new_data, version =new_version WHERE version = old_version

if (updated row > 0) {

// 樂觀鎖獲取成功,操作完成

} else {

// 樂觀鎖獲取失敗,回滾並重試

}

樂觀鎖在不發生取鎖失敗的情況下開銷比悲觀鎖小,但是一旦發生失敗回滾開銷則比較大,因此適合用在取鎖失敗概率比較小的場景,可以提升系統併發效能

樂觀鎖還適用於一些比較特殊的場景,例如在業務操作過程中無法和資料庫保持連線等悲觀鎖無法適用的地方。

悲觀鎖(Pessimistic Lock)

悲觀鎖的特點是先獲取鎖,再進行業務操作,即“悲觀”的認為獲取鎖是非常有可能失敗的,因此要先確保獲取鎖成功再進行業務操作。通常所說的“一鎖二查三更新”即指的是使用悲觀鎖。通常來講在資料庫上的悲觀鎖需要資料庫本身提供支援,即通過常用的select … for update操作來實現悲觀鎖。當資料庫執行select forupdate時會獲取被select中的資料行的行鎖,因此其他併發執行的select for update如果試圖選中同一行則會發生排斥(需要等待行鎖被釋放),因此達到鎖的效果。select for update獲取的行鎖會在當前事務結束時自動釋放,因此必須在事務中使用。

這裡需要注意的一點是不同的資料庫對select for update的實現和支援都是有所區別的,例如oracle支援select for update no wait,表示如果拿不到鎖立刻報錯,而不是等待,MySQL就沒有no wait這個選項。另外MySQL還有個問題是selectfor update語句執行中所有掃描過的行都會被鎖上,這一點很容易造成問題。因此如果在MySQL中用悲觀鎖務必要確定走了索引,而不是全表掃描。

總結

悲觀鎖和樂觀鎖是資料庫用來保證資料併發安全防止更新丟失的兩種方法,例子在select... for update前加個事務就可以防止更新丟失。

樂觀鎖、悲觀鎖使用場景

  • 響應速度:如果需要非常高的響應速度,建議採用樂觀鎖方案,成功就執行,不成功就失敗,不需要等待其他併發去釋放鎖。
  • 衝突頻率:如果衝突頻率非常高,建議採用悲觀鎖,保證成功率,如果衝突頻率大,樂觀鎖會需要多次重試才能成功,代價比較大。
  • 重試代價:如果重試代價大,建議採用悲觀鎖。

死鎖

MyISAM中是不會產生死鎖的,因為MyISAM總是一次性獲得所需的全部鎖,要麼全部滿足,要麼全部等待。而在InnoDB中,鎖是逐步獲得的,就造成了死鎖的可能。

有多種方法可以避免死鎖,這裡只介紹常見的三種
1、如果不同程式會併發存取多個表,儘量約定以相同的順序訪問表,可以大大降低死鎖機會。
2、在同一個事務中,儘可能做到一次鎖定所需要的所有資源,減少死鎖產生概率;
3、對於非常容易產生死鎖的業務部分,可以嘗試使用升級鎖定顆粒度,通過表級鎖定來減少死鎖產生的概率;