由於在數據庫中,除了傳統的計算資源(如CPU、RAM、I/O等)的爭用之外,數據也是一種供須要用戶共享的資源python
當併發事務同時訪問一個共享的資源時,有可能致使數據不一致、數據無效等問題mysql
例如在上一篇介紹過的事務併發狀況下出現的讀現象: 髒讀、不可重複讀、幻讀等算法
爲了解決這些方法, 主流的數據庫軟件都提供了鎖機制, 以及事務隔離級別的概念sql
而鎖機制能夠將併發的數據訪問順序化, 以保證數據庫中數據的一致性和有效性數據庫
ps : 鎖衝突也是影響數據庫併發性能的一個重要因素, 對鎖對數據庫很是重要, 但也更加複雜安全
按照按鎖的粒度劃分 : 可分爲行級鎖、表級鎖、頁級鎖架構
按照級別劃分 : 可分爲共享鎖、排他鎖、意向鎖、間隙鎖(Next-Key)併發
按照使用方式分 : 可分爲樂觀鎖、悲觀鎖高併發
按照加鎖方式分 : 可分爲自動鎖、顯式鎖性能
按照操做劃分 : DDL鎖、DML鎖
其餘 : 死鎖、MVCC
DML鎖(data locks, 數據鎖),用於保護數據的完整性, 其中包括行級鎖(Row Locks (TX鎖))、表級鎖(table lock(TM鎖))
DDL鎖(dictionary locks,數據字典鎖), 用於保護數據庫對象的結構,如表、索引等的結構定義; 其中包排他DDL(Exclusive DDL lock)、共享DDL鎖(Share DDL lock),可中斷解析鎖(Breakable parse locks)
在DBMS中, 能夠按照鎖的粒度把數據庫鎖分爲行級鎖(Innodb引擎默認使用)、表級鎖(Myisam引擎默認使用)和頁級鎖(BDB引擎默認使用)
行級鎖是Mysql中鎖定粒度最細的一種鎖, 表示只針對當前操做的行進行加鎖; 行級鎖能大大減小數據庫操做的衝突; 其加鎖粒度最小, 但加鎖的開銷也最大; 行級鎖分爲共享鎖和排它鎖
開銷大, 加鎖慢; 會出現死鎖; 鎖定粒度最小, 發生鎖衝突的機率最低, 併發也最高
Innodb 引擎
"共享鎖(s)" : select * from [表名] where [條件] lock in share mode; "排它鎖(x)" : select * from [表名] where [條件] for update;
表級鎖是MySQL中鎖定粒度最大的一種鎖, 表示對當前操做的整張表加鎖, 它實現簡單, 資源消耗較少, 被大部分MySQL引擎支持; 最常使用的Myisam與Innodb都支持表級鎖定; 表級鎖定分爲表共享讀鎖(共享鎖)和表獨佔寫鎖(排它鎖)
開銷小, 加鎖快; 不會出現死鎖; 鎖定粒度大, 發出鎖衝突的機率最高, 併發度最低
Myisam引擎、Memory引擎、Innodb引擎
"語法" : lock table [表名1] [resd|write],[表名2] [resd|write], ...; # 能夠加讀鎖或者寫做 lock table user read; # 將表 user 加上寫鎖 show open tables where in_user>=1; # 查看當前會話鎖定一次以上的表 update user set name="song" where id=1; # 更新表數據(會提示表被鎖定) unlock tables; # 釋放當前會話持有的任何鎖 update user set name="song" where id=1; # 再次更新能夠成功
頁級鎖是MySQL中鎖定粒度介於行級鎖和表級鎖中間的一種鎖; 表級鎖速度快, 但衝突多, 行級衝突少, 但速度慢; 因此取了折衷的頁級, 一次鎖定相鄰的一組記錄; BDB支持頁級鎖
開銷和加鎖時間界於表鎖和行鎖之間; 會出現死鎖; 鎖定粒度界於表鎖和行鎖之間, 併發度通常
BDB引擎
InnoDB行鎖不是直接鎖記錄, 而是鎖索引, 這一點MySQL與Oracle不一樣, 後者是經過在數據塊中對相應數據行加鎖來實現的
InnoDB這種行鎖實現特色意味着:只有經過索引條件檢索數據, InnoDB才使用行級鎖, 不然,InnoDB將鎖住全部行, 實現的效果至關因而表鎖
演示
create table t01(id int,name char(16)); # 建立表(而且不添加索引) insert t01 value(1,"aa"),(2,"bb"),(3,"cc"),(4,"dd"); # 插入記錄 # 開啓兩個會話窗口, 分別手動開啓事務 # 事務1對 id=2 進行鎖行操做, 事務2對 id!=2 的行進行更新 # 發現阻塞, 一段時間後顯示超時 : ERROR 1205 (HY000): Lock wait timeout exceeded;....
create index index_id on t01(id); # 爲 id 字段建立索引 desc t01; # 查看錶結構 # 再次重複上面開啓事務的步驟
行鎖鎖的是索引, 索引又分爲主鍵索引和非主鍵索引兩種, 因此鎖定的方式分爲如下三種 :
在實際應用中, 要特別注意InnoDB行鎖的這一特性, 不然可能致使大量的鎖衝突, 從而影響併發性能 :
與對行處理有關的語句有 : insert
、update
、delete
、select
, 這四類語句在操做記錄時, 均可覺得行加上鎖, 但須要注意的是 :
# 手動開啓事務1,對 id=1 的記錄進行增刪改操做, 而且爲提交狀態 # 開啓事務2,也對 id=1 的記錄進行增刪改操做 # 發現阻塞在原地,一段時間後顯示超時 : ERROR 1205 (HY000): Lock wait timeout exceeded ....
"共享鎖(s)" : select * from [表名] where [條件] lock in share mode; "排它鎖(x)" : select * from [表名] where [條件] for update;
共享鎖又稱爲讀鎖, 簡稱S鎖, 顧名思義, 共享鎖就是多個事務對於同一數據能夠共享一把鎖, 獲准共享鎖的事務只能讀數據, 不能修改數據直到已釋放全部共享鎖, 因此共享鎖能夠支持併發讀
若是事務1對數據A加上共享鎖後, 則其餘事務只能對A再加共享鎖或不加鎖 (在其餘事務裏必定不能再加排他鎖, 可是在事務1本身裏面是能夠加的), 反之亦然
select * from [表名] where [條件] lock in share mode;
在查詢語句後面增長
lock in share mode
,Mysql會對查詢結果中的每行都加共享鎖,當沒有其餘線程對查詢結果集中的任何一行使用排他鎖時, 能夠成功申請共享鎖, 不然會被阻塞; 其餘線程也能夠讀取使用了共享鎖的表, 並且這些線程讀取的是同一個版本的數據
排他鎖又稱爲寫鎖, 簡稱X鎖, 顧名思義, 排他鎖就是不能與其餘所並存, 如一個事務獲取了一個數據行的排他鎖, 其餘事務就不能再對該行加任何類型的其餘他鎖 (共享鎖和排他鎖), 可是獲取排他鎖的事務是能夠對數據就行讀取和修改
select * from [表名] where [條件] for update;
在查詢語句後面增長
for update
, Mysql會對查詢結果中的每行都加排他鎖, 當沒有其餘線程對查詢結果集中的任何一行使用排他鎖時, 能夠成功申請排他鎖, 不然會被阻塞
加過排他鎖的數據行在其餘事務種是不能修改數據的, 也不能經過
for update
和lock in share mode
鎖的方式查詢數據, 但能夠直接經過select ...from...
查詢數據, 由於普通select查詢沒有任何鎖機制
創建了索引且命中的狀況下:
意向鎖是表級鎖, 其設計目的主要是爲了在一個事務中揭示下一行將要被請求鎖的類型
當一個事務在須要獲取資源鎖定的時候, 若是遇到本身須要的資源已經被排他鎖佔用的時候, 該事務能夠須要鎖定行的表上面添加一個合適的意向鎖
若是本身須要一個共享鎖, 那麼就在表上面添加一個意向共享鎖; 而若是本身須要的是某行(或者某些行)上面添加一個排他鎖的話, 則先在表上面添加一個意向排他鎖
- 意向共享鎖(IS) : 事務打算給數據行共享鎖; 事務在給一個數據行加共享鎖前必須先取得該表的IS鎖
- 意向排他鎖(IX) : 事務打算給數據行加排他鎖; 事務在給一個數據行加排他鎖前必須先取得該表的IX鎖
ps : 意向鎖是InnoDB自動加的,不須要用戶干預
絕大部分狀況使用行鎖, 但在個別特殊事務中, 也能夠考慮使用表鎖
若使用默認的行鎖,不只該事務執行效率低(由於須要對較多行加鎖,加鎖是須要耗時的); 並且可能形成其餘事務長時間鎖等待和鎖衝突; 這種狀況下能夠考慮使用表鎖來提升該事務的執行速度
這種狀況也能夠考慮一次性鎖定事務涉及的表, 從而避免死鎖、減小數據庫因事務回滾帶來的開銷固然, 應用中這兩種事務不能太多, 不然, 就應該考慮使用Myisam
經過檢查 InnoDB_row_lock 狀態變量來分析系統上的行鎖的爭奪狀況, 在着手根據狀態量來分析改善
show status like "innodb_row_lock%"; # 查看行鎖狀態
Innodb 四種鎖定模式的共存邏輯關係 :
共享鎖(S) | 排他鎖(X) | 意向共享鎖(IS) | 意向排他鎖(Ⅸ) | |
---|---|---|---|---|
共享鎖(S) | 兼容 | 衝突 | 兼容 | 衝突 |
排他鎖(X) | 衝突 | 衝突 | 衝突 | 衝突 |
意向共享鎖(IS) | 兼容 | 衝突 | 兼容 | 兼容 |
意向排他鎖(Ⅸ) | 衝突 | 衝突 | 兼容 | 兼容 |
若是一個事務請求的鎖模式與當前的鎖兼容,InnoDB就將請求的鎖授予該事務;反之,若是二者不兼容,該事務就要等待鎖釋放
select * from emp where id>=100 for update;
語句的時候, 它是一個範圍條件檢索, 而且命中了索引, InnoDB不只會對符合條件的emp_id值爲100的記錄加鎖, 也會對em_pid大於100 (這些記錄並不存在) 的"間隙"加鎖ps : 對於行的查詢, Innodb採用的都是Next-Key Lock, 主要目的是解決幻讀的問題, 以知足相關隔離級別以及恢復和複製的須要
MyISAM中是不會產生死鎖的,由於MyISAM老是一次性得到所需的所有鎖,要麼所有知足,要麼所有等待。而在InnoDB中,鎖是逐步得到的,就形成了死鎖的可能
create index index_id on t01; # 刪除以前的索引 alter table t01 modify id int primary ket; # 將id字段設置成主鍵 create index index_id on t01(id); # 建立彙集索引 create index index_name on t01(name); # 建立輔助索引(非彙集索引) desc t01; # 查看錶結構
第二個死鎖問題,只有多個事務同時運行的狀況下才可能出現,但隱蔽性極強,雖然每一個Session都只有一條語句,仍舊會產生死鎖。要分析這個死鎖,首先必須用到本文前面提到的MySQL加鎖的規則。針對Session 1,從name索引出發,讀到的[hdc, 1],[hdc, 6]均知足條件,不只會加name索引上的記錄X鎖,並且會加聚簇索引上的記錄X鎖,加鎖順序爲先[1,hdc,100],後[6,hdc,10]。而Session 2,從pubtime索引出發,[10,6],[100,1]均知足過濾條件,一樣也會加聚簇索引上的記錄X鎖,加鎖順序爲[6,hdc,10],後[1,hdc,100]。發現沒有,跟Session 1的加鎖順序正好相反,若是兩個Session剛好都持有了第一把鎖,請求加第二把鎖,死鎖就發生了
- 在MySQL中, 行級鎖並非直接鎖記錄, 而是鎖索引; 索引分爲主鍵索引和非主鍵索引兩種
- 若是一條sql語句操做了主鍵索引, MySQL就會鎖定這條主鍵索引
- 若是一條語句操做了非主鍵索引, MySQL會先鎖定該非主鍵索引, 再鎖定相關的主鍵索引
- 在update、delete操做時, MySQL不只鎖定WHERE條件掃描過的全部索引記錄, 並且會鎖定相鄰的鍵值, 即所謂的next-key locking
- 死鎖的發生與否, 並不在於事務中有多少條SQL語句, 死鎖的關鍵在於 : 兩個(或以上)的Session加鎖的順序不一致
- 而使用上面提到的, 分析MySQL每條SQL語句的加鎖規則, 分析出每條語句的加鎖順序
- 而後檢查多個併發SQL間是否存在以相反的順序加鎖的狀況, 就能夠分析出各類潛在的死鎖狀況, 也能夠分析出線上死鎖發生的緣由
發生死鎖後, InnoDB通常均可以檢測到, 並使一個事務釋放鎖回退, 另外一個獲取鎖完成事務, 上面實驗也證實了, 但也有多種方法能夠避免死鎖:
數據庫管理系統 (DBMS) 中的併發控制的任務是確保在多個事務同時存取數據庫中同一數據時不破壞事務的隔離性和統一性以及數據庫的統一性
樂觀併發控制 (樂觀鎖) 和悲觀併發控制 (悲觀鎖) 是併發控制主要採用的技術手段。
不管是悲觀鎖仍是樂觀鎖, 都是人們定義出來的概念, 能夠認爲是一種思想; 其實不只僅是關係型數據庫系統中有樂觀鎖和悲觀鎖的概念, 像memcache、hibernate、tair等都有相似的概念
針對於不一樣的業務場景, 應該選用不一樣的併發控制方式; 因此, 不要把樂觀併發控制和悲觀併發控制狹義的理解爲DBMS中的概念, 更不要把他們和數據中提供的鎖機制 (行鎖、表鎖、排他鎖、共享鎖) 混爲一談; 其實, 在DBMS中, 悲觀鎖正是利用數據庫自己提供的鎖機制來實現的
悲觀的認爲操做數據庫就是修改數據, 爲了不數據庫中的數據同時被修改, 直接對該操做加鎖處理
當咱們要對一個數據庫中的一條數據進行修改的時候, 爲了不同時被其餘人修改, 最好的辦法就是直接對該數據進行加鎖以防止併發
這種藉助數據庫鎖機制在修改數據以前先鎖定, 再修改的方式被稱之爲悲觀併發控制 (又名「悲觀鎖」,Pessimistic Concurrency Control, 縮寫「PCC」)
在關係數據庫管理系統裏,悲觀併發控制 (又名「悲觀鎖」,Pessimistic Concurrency Control,縮寫「PCC」) 是一種併發控制的方法
它能夠阻止一個事務以影響其餘用戶的方式來修改數據; 若是一個事務執行的操做都某行數據應用了鎖,那只有當這個事務把鎖釋放, 其餘事務纔可以執行與該鎖衝突的操做
悲觀併發控制主要用於數據爭用激烈的環境, 以及發生併發衝突時使用鎖保護數據的成本要低於回滾事務的成本的環境中
悲觀鎖, 正如其名, 它指的是對數據被外界 (包括本系統當前的其餘事務, 以及來自外部系統的事務處理) 修改持保守態度(悲觀), 所以, 在整個數據處理過程當中, 將數據處於鎖定狀態;
悲觀鎖的實現, 每每依靠數據庫提供的鎖機制 (也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性, 不然, 即便在本系統中實現了加鎖機制, 也沒法保證外部系統不會修改數據) , 如今互聯網高併發的架構中, 受到 fail-fast 思路的影響, 悲觀鎖已經很是少見了
在對任意記錄進行修改前, 先嚐試爲該記錄加上排他鎖 (exclusive locking)
若是加鎖失敗, 說明該記錄正在被修改, 那麼當前查詢可能要等待或者拋出異常; 具體響應方式由開發者根據實際須要決定
若是成功加鎖, 那麼就能夠對記錄作修改, 事務完成後就會解鎖了
其間若是有其餘對該記錄作修改或加排他鎖的操做, 都會等待咱們解鎖或直接拋出異常
ps : 行鎖、表鎖、讀鎖、寫鎖都是在操做以前先上排他鎖
悲觀併發控制主要用於數據爭用激烈的環境, 以及發生併發衝突時使用鎖保護數據的成本要低於回滾事務的成本的環境中
悲觀併發控制其實是「先取鎖再訪問」的保守策略, 爲數據處理的安全提供了保證
- 在效率方面, 處理加鎖的機制會讓數據庫產生額外的開銷, 還有增長產生死鎖的機會
- 在只讀型事務處理中因爲不會產生衝突, 也不必使用鎖, 這樣作只能增長系統負載
- 會下降了並行性, 一個事務若是鎖定了某行數據, 其餘事務就必須等待該事務處理完才能夠處理那行數
樂觀的認爲操做數據庫不會形成衝突, 只有在對數據進行更新的時候纔會進行校驗. 若是衝突就返回錯誤讓用戶決定如何去作
- 在關係數據庫管理系統裏, 樂觀併發控制 (又名「樂觀鎖」, Optimistic Concurrency Control, 縮寫「OCC」) 是一種併發控制的方法
- 它假設多用戶併發的事務在處理時不會彼此互相影響, 各事務可以在不產生鎖的狀況下處理各自影響的那部分數據
- 在提交數據更新以前, 每一個事務會先檢查在該事務讀取數據後, 有沒有其餘事務又修改了該數據
- 若是其餘事務有更新的話, 正在提交的事務會進行回滾; 樂觀事務控制最先是由孔祥重 (H.T.Kung) 教授提出
樂觀鎖 (Optimistic Locking) 相對悲觀鎖而言, 樂觀鎖假設認爲數據通常狀況下不會形成衝突, 因此在數據進行提交更新的時候, 纔會正式對數據的衝突與否進行檢測, 若是發現衝突了, 則讓返回用戶錯誤的信息, 讓用戶決定如何去作
相對於悲觀鎖, 在對數據庫進行處理的時候, 樂觀鎖並不會使用數據庫提供的鎖機制; 通常的實現樂觀鎖的方式就是記錄數據版本
數據版本 : 爲數據增長的一個版本標識, 當讀取數據時, 將版本標識的值一同讀出, 數據每更新一次, 同時對版本標識進行更新
當咱們提交更新的時候, 判斷數據庫表對應記錄的當前版本信息與第一次取出來的版本標識進行比對, 若是數據庫表當前版本號與第一次取出來的版本標識值相等, 則予以更新, 不然認爲是過時數據
每一行數據多一個字段version, 每次更新數據對應版本號+1,
原理 : 讀出數據, 將版本號一同讀出, 以後更新, 版本號+1, 提交數據版本號大於數據庫當前版本號, 則予以更新, 不然認爲是過時數據, 從新讀取數據
每一行數據多一個字段 time
原理 : 讀出數據, 將時間戳一同讀出, 以後更新, 提交數據時間戳等於數據庫當前時間戳, 則予以更新, 不然認爲是過時數據, 從新讀取數據
樂觀併發控制相信事務之間的數據競爭(data race)的機率是比較小的, 所以儘量直接作下去, 直到提交的時候纔去鎖定, 因此不會產生任何鎖和死鎖
二者的區別於使用場景 :
隨着互聯網三高架構 (高併發、高性能、高可用) 的提出, 悲觀鎖已經愈來愈少的被使用到生產環境中了, 尤爲是併發量比較大的業務場景