一次封鎖or兩段鎖
因為有大量的并發(fā)訪問,為了預防死鎖,一般應用中推薦使用一次封鎖法,就是在方法的開始階段,已經預先知道會用到哪些數(shù)據(jù),然后全部鎖住,在方法運行之后,再全部解鎖。這種方式可以有效的避免循環(huán)死鎖,但在數(shù)據(jù)庫中卻不適用,因為在事務開始階段,數(shù)據(jù)庫并不知道會用到哪些數(shù)據(jù)。
數(shù)據(jù)庫遵循的是兩段鎖協(xié)議,將事務分成兩個階段,加鎖階段和解鎖階段(所以叫兩段鎖)
加鎖階段:
在該階段可以進行加鎖操作。在對任何數(shù)據(jù)進行讀操作之前要申請并獲得S鎖(共享鎖,其它事務可以繼續(xù)加共享鎖,但不能加排它鎖),在進行寫操作之前要申請并獲得X鎖(排它鎖,其它事務不能再獲得任何鎖)。加鎖不成功,則事務進入等待狀態(tài),直到加鎖成功才繼續(xù)執(zhí)行。
解鎖階段:
當事務釋放了一個封鎖以后,事務進入解鎖階段,在該階段只能進行解鎖操作不能再進行加鎖操作。
事務的四種隔離級別
隔離級別 | 臟讀(Dirty Read) | 不可重復讀(NonRepeatable Read) | 幻讀(Phantom Read) |
---|---|---|---|
未提交讀(Read uncommitted) | 可能 | 可能 | 可能 |
已提交讀(Read committed) | 不可能 | 可能 | 可能 |
可重復讀(Repeatable read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable ) | 不可能 | 不可能 | 不可能 |
- 未提交讀(Read Uncommitted):允許臟讀,也就是可能讀取到其他會話中未提交事務修改的數(shù)據(jù)
- 提交讀(Read Committed):只能讀取到已經提交的數(shù)據(jù)。Oracle等多數(shù)數(shù)據(jù)庫默認都是該級別 (不重復讀)
- 可重復讀(Repeated Read):可重復讀。在同一個事務內的查詢都是事務開始時刻一致的,InnoDB默認級別。在SQL標準中,該隔離級別消除了不可重復讀,但是還存在幻象讀
- 串行讀(Serializable):完全串行化的讀,每次讀都需要獲得表級共享鎖,讀寫相互都會阻塞
快照讀:簡單的select操作,屬于快照讀,不加鎖。(當然,也有例外,下面會分析)
- select * from table where ?;
當前讀:特殊的讀操作,插入/更新/刪除操作,屬于當前讀,需要加鎖。
- select * from table where ? lock in share mode;
- select * from table where ? for update;
- insert into table values (…);
- update table set ? where ?;
- delete from table where ?;
一個Update操作的具體流程。當Update SQL被發(fā)給MySQL后,MySQL Server會根據(jù)where條件,讀取第一條滿足條件的記錄,然后InnoDB引擎會將第一條記錄返回,并加鎖 (current read)。待MySQL Server收到這條加鎖的記錄之后,會再發(fā)起一個Update請求,更新這條記錄。
一條記錄操作完成,再讀取下一條記錄,直至沒有滿足條件的記錄為止。
因此,Update操作內部,就包含了一個當前讀。同理,Delete操作也一樣。Insert操作會稍微有些不同,簡單來說,就是Insert操作可能會觸發(fā)Unique Key的沖突檢查,也會進行一個當前讀。
MySQL/InnoDB定義的4種隔離級別:
Read Uncommited
可以讀取未提交記錄。此隔離級別,不會使用,忽略。
Read Committed (RC)
快照讀忽略,本文不考慮。
針對當前讀,RC隔離級別保證對讀取到的記錄加鎖 (記錄鎖),存在幻讀現(xiàn)象。
Repeatable Read (RR)
快照讀忽略,本文不考慮。
針對當前讀,RR隔離級別保證對讀取到的記錄加鎖 (記錄鎖),同時保證對讀取的范圍加鎖,新的滿足查詢條件的記錄不能夠插入 (間隙鎖),不存在幻讀現(xiàn)象。
Serializable
從MVCC并發(fā)控制退化為基于鎖的并發(fā)控制。不區(qū)別快照讀與當前讀,所有的讀操作均為當前讀,讀加讀鎖 (S鎖),寫加寫鎖 (X鎖)。
Serializable隔離級別下,讀寫沖突,因此并發(fā)度急劇下降,在MySQL/InnoDB下不建議使用。