(一)鎖
當數據庫有併發事務的時候,可能會產生數據不一致,這時候需要一些機制來保證訪問次序,鎖機制就是這樣的一個機制。
1)隔離級別與鎖的關係
- 在ReadUncommitted級別下,讀取數據不需要加共享鎖,這樣就不會和被修改的數據上的排他鎖衝突。
- 在ReadCommitted級別下,讀操作需要加共享鎖,但是在語句執行完之後釋放共享鎖。
- 在RepeatableRead級別下,讀操作需要加共享鎖,但是在事務提交之前並不釋放共享鎖,即在事務執行完畢以後才釋放共享鎖。
- Serializable是限制性最強的隔離級別,因爲該級別鎖定整個範圍的鍵,並一直持有鎖,直到事務完成。
2)數據庫鎖粒度
在關係型數據庫中可以按照鎖的粒度把數據庫鎖分爲行級鎖(InnoDB引擎)、表級鎖(MyISAM引擎)和頁級鎖(BDB引擎)。
(1)行級鎖
行級鎖是MySQL中鎖定粒度最細的一種鎖,只針對當前操作的行進行加鎖。行級鎖能大大減少數據庫操作的衝突,加鎖粒度最小,單加鎖的開銷也最大。行級鎖分爲共享鎖和排他鎖。
特點:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度最高。
(2)表級鎖
表級鎖是MySQL中鎖定粒度最大的一種鎖,表示對當前操作的整張表加鎖。實現簡單,資源消耗較少,被大部分MySQL引擎支持。最常使用的MyISAM和InnoDB都支持表級鎖定。表級鎖分爲表共享鎖(共享鎖)和表獨佔寫鎖(排他鎖)。
特點:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發出鎖衝突的概率最高,併發度低。
(3)頁級鎖
頁級鎖是MySQL中鎖定粒度介於行鎖和表鎖中間的一種鎖。表鎖速度快但衝突多,行鎖衝突少但速度慢,所以取了折中的頁級,一次鎖定相鄰的一組記錄。
特點:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度介於表鎖和行鎖之間,併發度一般。
(4)對比
表鎖:開銷小、加鎖快、不會出現死鎖、鎖粒度較大、發生鎖衝突的概率比較大。
行鎖:開銷大、加鎖慢、會出現死鎖、鎖粒度較小、發生鎖衝突的概率較小。
頁鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度介於表鎖和行鎖之間,併發度一般。
3)數據庫鎖類別
從鎖的類別上來講,有共享鎖和排他鎖。
(1)共享鎖
共享鎖,又叫做讀鎖。當用戶要進行數據的讀取時,對數據加上共享鎖。共享鎖可以同時加上多個。
(2)排他鎖
排他鎖就叫做寫鎖。當用戶要進行數據的寫入時對數據加上排他鎖,排他鎖只可以加一個,和其他的共享鎖、排他鎖都相斥。
(二)引擎的鎖
1)MyISAM表鎖
- MyISAM支持表鎖,不支持事務處理、不支持外鍵。
- MyISAM併發比較簡單,只支持表鎖粒度,鎖的粒度較大。但是不會引起死鎖,它支持表共享地讀鎖和表互斥地寫鎖。
- MyISAM表的讀操作:不會阻塞其他用戶對同一張表的讀操作,但是阻塞其他用戶對同一張表的寫操作。
- MyISAM表的寫操作:會阻塞其他用戶對同一個表的讀、寫操作。
- MyISAM的讀寫互斥、寫寫互斥,讀讀共享。
- 粒度:可控制範圍(表、行)
2)InnoDB行鎖
- InnoDB支持事務,支持外鍵,支持行級鎖,併發程度高。
- InnoDB支持兩種類型的行鎖:
- 1)共享鎖(S):允許一個事務去讀一行,組織其他事務獲取相同的數據集的排他鎖。
- 2)排他鎖(X):允許獲得其他鎖的事務更新數據,組織其他事務獲取懸停數據集的共享鎖和排他鎖。
- InnoDB中行鎖是通過給索引上的索引選項加鎖實現的,而不是給表中的行記錄進行加鎖。意味着如果表中的行不存在索引,InnoDB使用表鎖來實現。
InnoDB是基於索引來完成行鎖
例如select * from tab_with_index where id = 1 for update;
for tpdate可以根據條件來完成行鎖鎖定,並且id是有索引鍵的列,如果id不是索引鍵,那麼InnoDB將完成表鎖。
- InnoDB會產生死鎖,例如:對錶tb1、tb2進行數據查詢,有兩個窗口進行操作,設置爲手動提交事務。
- 窗口1:先查詢tb1,再查詢tb2,然後進行事務提交commit。
- 窗口2:先查詢tb2,再查詢tb1,然後進行事務提交commit。
3)InnoDB存儲索引的鎖的算法
- Record lock:單個行記錄上的鎖
- Gap lock:間隙鎖,鎖定一個範圍,不包括記錄本身。
- Next-key lock:record+gap鎖定一個範圍,包含記錄本身。
4)相關補充
- InnoDB對於行的查詢使用Next-key lock。
- Next-locking keying爲了解決Phantom Problem幻讀問題。
- 當查詢的索引含有唯一屬性時,將next-key lock降級爲record key。
- Gap鎖設計的目的是爲了阻止多個事務將記錄插入到同一範圍內,這會導致幻讀問題的產生。
- 有兩種方式顯式關閉gap鎖:除了外鍵約束和唯一性檢查外,其餘情況僅使用record lock
A、將事務隔離級別設置爲RC。
B、將參數innodb_locks_unsafe_for_binlog設置爲1。
(三)數據庫死鎖
死鎖是指兩個或多個事務在同一資源上相互佔用,並請求鎖定對方的資源,從而導致惡性循環的現象。
1)常見的解決方法
- 如果不同程序會併發存取多個表,儘量約定以相同的順序訪問表,可以大大降低死鎖機會。
- 在同一個事務中,儘可能做到一次鎖定所需要的所有資源,減少死鎖產生概率。
- 對於非常容易產生死鎖的業務部分,可以嘗試使用升級鎖定粒度,通過表級鎖來減少死鎖產生的概率。
- 如果業務處理不好,可以使用分佈式事務鎖或者樂觀鎖。
2)樂觀鎖悲觀鎖
數據庫管理系統(DBMS)中的併發控制的任務是,確保在多個事務同時存取數據庫中同一數據時,不破壞事務的隔離性和統一性以及數據庫的統一性。樂觀鎖和悲觀鎖是兩種主要的技術手段。
(1)介紹
- 悲觀鎖:假設一定會發生併發衝突,屏蔽一切可能違反數據完整性的操作。在查詢完數據的時候就把事務鎖起來,直到提交事務。
實現方式:使用數據庫中的鎖機制。
- 樂觀鎖假設不會發生併發衝突,只在提交操作時檢查是否違反數據完整性。在修改數據的時候把事務鎖起來,通過version的方式來進行鎖定。
實現方式:一般使用版本號控制或者CAS算法實現。
(2)使用場景
樂觀鎖適用於寫比較少的情況下(多讀場景)。即衝突真的很少發生的時候,這樣可以省去鎖的開銷,加大系統的吞吐量。
但如果是多寫的情況,一般會經常產生衝突,這樣會導致上層應用會不斷的進行retry,這樣反而降低性能,所以在多寫的場景下使用悲觀鎖比較合適。