一、事務的4大特性(ACID)
1. 原子性(Atomicity)
事務是資料庫的邏輯工作機關,它對資料庫的修改要麼全部執行,要麼全部不執行。
2. 一緻性(Consistemcy)
事務前後,資料庫的狀态都滿足所有的完整性限制。
3. 隔離性(Isolation)
并發執行的事務是隔離的,不會互相影響。如果有兩個事務,運作在相同的時間内,執行相同的功能,事務的隔離性将確定每一事務在系統中認為隻有該事務在使用系統。這種屬性有時稱為串行化,為了防止事務操作間的混淆,必須串行化或序列化請求,使得在同一時間僅有一個請求用于同一資料。通過設定資料庫的隔離級别,可以達到不同的隔離效果。
4. 持久性(Durability)
指的是隻要事務成功結束,它對資料庫所做的更新就必須永久儲存下來。即使發生系統崩潰,重新啟動資料庫系統後,資料庫還能恢複到事務成功結束時的狀态。
二、并發事務引起的問題
1. 更新丢失(Lost Update)
兩個事務都同時更新一行資料,但是第二個事務卻中途失敗退出,導緻對資料的兩個修改都失效了。這是因為系統沒有執行任何的鎖操作,是以并發事務并沒有被隔離開來。
2. 髒讀(Dirty Read)
又稱無效資料讀出。一個事務讀取另外一個事務還沒有送出的資料叫髒讀。
例如:事務T1修改了一行資料,但是還沒有送出,這時候事務T2讀取了被事務T1修改後的資料,之後事務T1因為某種原因Rollback了,那麼事務T2讀取的資料就是髒的。
3. 不可重複讀(Non-Repeatable Read)
是指在一個事務中兩次讀同一行資料,可是這兩次讀到的資料不一樣。
例如:事務T1讀取某一資料,事務T2讀取并修改了該資料,T1為了對讀取值進行檢驗而再次讀取該資料,便得到了不同的結果。
4. 幻讀
事務在操作過程中進行兩次查詢,第二次查詢的結果包含了第一次查詢中未出現的資料或者缺少了第一次查詢中出現的資料
例如:系統管理者A将資料庫中所有學生的成績從具體分數改為ABCDE等級,但是系統管理者B就在這個時候插入了一條具體分數的記錄,當系統管理者A改結束後發現還有一條記錄沒有改過來,就好像發生了幻覺一樣。這就叫幻讀。
不可重複讀重點在于update和delete,而幻讀的重點在于insert。是以說不可重複讀和幻讀最大的差別,就在于如何通過鎖機制來解決他們産生的問題。
三、事務的隔離級别
以上的4種問題(更新丢失、髒讀、不可重複讀、幻讀)都和事務的隔離級别有關。通過設定事務的隔離級别,可以避免上述問題的發生。
1. 讀未送出(Read Uncommitted)
讀事務不阻塞其他讀事務和寫事務,未送出的寫事務阻塞其他寫事務但不阻塞讀事務。
此隔離級别可以防止更新丢失,但不能防止髒讀、不可重複讀、幻讀。
此隔離級别可以通過“排他寫鎖”實作。
2. 讀已送出(Read Committed)
讀事務允許其他讀事務和寫事務,未送出的寫事務禁止其他讀事務和寫事務。
此隔離級别可以防止更新丢失、髒讀,但不能防止不可重複讀、幻讀。
此隔離級别可以通過“瞬間共享讀鎖”和“排他寫鎖”實作。
3. 可重複讀取(Repeatable Read)
以操作同一行資料為前提,讀事務禁止其他寫事務但不阻塞讀事務,未送出的寫事務禁止其他讀事務和寫事務。
此隔離級别可以防止更新丢失、髒讀、不可重複讀,但不能防止幻讀。
此隔離級别可以通過“共享讀鎖”和“排他寫鎖”實作。
4. 序列化(Serializable)
提供嚴格的事務隔離,它要求事務序列化執行,事務隻能一個接着一個地執行,不能并發執行。
此隔離級别可以防止更新丢失、髒讀、不可重複讀、幻讀。
如果僅僅通過“行級鎖”是無法實作事務序列化的,必須通過其他機制保證新插入的資料不會被剛執行查詢操作的事務通路到。
可串行化:如果一個并行排程的結果等價于某一個串行排程的結果,那麼這個并行排程是可串行化的。
隔離級别越高,越能保證資料的完整性和一緻性,但是對并發性能的影響也越大。對于多數應用程式,可以優先考慮把資料庫系統的隔離級别設為Read Committed。它能夠避免更新丢失、髒讀,而且具有較好的并發性能。盡管它會導緻不可重複讀、幻讀這些并發問題,在可能出現這類問題的個别場合,可以由應用程式采用悲觀鎖或樂觀鎖來控制。
事務隔離級别 | 復原覆寫 | 髒讀 | 不可重複讀 | 送出覆寫 | 幻讀 |
讀未送出 | x | 可能發生 | 可能發生 | 可能發生 | 可能發生 |
讀已送出 | x | x | 可能發生 | 可能發生 | 可能發生 |
可重複讀 | x | x | x | x | 可能發生 |
串行化 | x | x | x | x | x |
四、常用的解決方案
1. 版本檢查
在資料庫中保留“版本”字段,跟随資料同時讀寫,以此判斷資料版本。版本可能是時間戳或狀态字段。
下例中的 WHERE 子句就實作了簡單的版本檢查:
UPDATE table SET status = 1 WHERE id=1 AND status = 0;
版本檢查能夠作為“樂觀鎖”,解決更新丢失的問題。
2. 鎖
2.1 共享鎖與排它鎖
共享鎖(Shared locks, S-locks)
共享鎖又稱讀鎖,是讀取操作建立的鎖。其他使用者可以并發讀取資料,但任何事務都不能對資料進行修改(擷取資料上的排他鎖),直到已釋放所有共享鎖。
能給未加鎖和添加了S鎖的對象添加S鎖。對象可以接受添加多把S鎖。
如果事務T對資料A加上共享鎖後,則其他事務隻能對A再加共享鎖,不能加排他鎖。獲準共享鎖的事務隻能讀資料,不能修改資料。
用法:
SELECT ... LOCK IN SHARE MODE;
在查詢語句後面增加LOCK IN SHARE MODE,Mysql會對查詢結果中的每行都加共享鎖,當沒有其他線程對查詢結果集中的任何一行使用排他鎖時,可以成功申請共享鎖,否則會被阻塞。其他線程也可以讀取使用了共享鎖的表,而且這些線程讀取的是同一個版本的資料。
排它鎖(Exclusive locks, X-locks)
排他鎖又稱寫鎖,如果事務T對資料A加上排他鎖後,則其他事務不能再對A加任任何類型的封鎖。獲得排他鎖的事務既能讀資料,又能修改資料。
隻能給未加鎖的對象添加X鎖。對象隻能接受一把X鎖。加X鎖的對象不能再加任何鎖。
用法:
SELECT ... FOR UPDATE;
在查詢語句後面增加FOR UPDATE,Mysql會對查詢結果中的每行都加排他鎖,當沒有其他線程對查詢結果集中的任何一行使用排他鎖時,可以成功申請排他鎖,否則會被阻塞。
對于insert、update、delete,InnoDB會自動給涉及的資料加排他鎖(X);對于一般的Select語句,InnoDB不會加任何鎖,事務可以通過以下語句給顯示加共享鎖或排他鎖。
共享鎖:SELECT ... LOCK IN SHARE MODE;
排他鎖:SELECT ... FOR UPDATE;
2.2 意向鎖
InnoDB還有兩個表鎖:
意向共享鎖(IS):表示事務準備給資料行加入共享鎖,也就是說一個資料行加共享鎖前必須先取得該表的IS鎖。
意向排他鎖(IX):類似上面,表示事務準備給資料行加入排他鎖,說明事務在一個資料行加排他鎖前必須先取得該表的IX鎖。
意向鎖是InnoDB自動加的,不需要使用者幹預。
2.3 臨時鎖與持續鎖
鎖的時效性,指明了加鎖生效期是到目前語句結束還是目前事務結束。
2.4 表級鎖與行級鎖
鎖的粒度,指明了加鎖的對象是目前表還是目前行。
2.5 悲觀鎖與樂觀鎖
悲觀鎖(Pessimistic Locking)
悲觀鎖假定目前事務操縱資料資源時,肯定還會有其他事務同時通路該資料資源,為了避免目前事務的操作受到幹擾,先鎖定資源。悲觀鎖需使用資料庫的鎖機制實作,如使用行級排他鎖或表級排它鎖。
盡管悲觀鎖能夠防止丢失更新和不可重複讀這類問題,但是它非常影響并發性能,是以應該謹慎使用。
樂觀鎖(Optimistic Locking)
樂觀鎖假定目前事務操縱資料資源時,不會有其他事務同時通路該資料資源,是以不在資料庫層次上的鎖定。樂觀鎖使用由程式邏輯控制的技術來避免可能出現的并發問題。
唯一能夠同時保持高并發和高可伸縮性的方法就是使用帶版本檢查的樂觀鎖。
樂觀鎖不能解決髒讀的問題,是以仍需要資料庫至少啟用“讀已送出”的事務隔離級别。
3. 三級加鎖協定
三級加鎖協定也稱為三級封鎖協定,是為了保證正确的排程事務的并發操作,事務在對資料庫對象加鎖,解鎖是必須遵守的一種規則。
3.1 一級加鎖協定
事務在修改資料前必須加X鎖,直到事務結束(事務結束包括正常結束(COMMIT)和非正常結束(ROLLBACK))才可釋放;如果僅僅是讀資料,不需要加鎖。
如下例:
SELECT xxx FOR UPDATE;
UPDATE xxx;
一級封鎖協定可以防止丢失修改,并保證事務T是可恢複的。使用一級封鎖協定可以解決丢失修改問題。
在一級封鎖協定中,如果僅僅是讀資料不對其進行修改,是不需要加鎖的,它不能保證可重複讀和不讀“髒”資料。
3.2 二級加鎖協定
滿足一級加鎖協定,且事務在讀取資料之前必須先加S鎖,讀完後即可釋放S鎖。
二級封鎖協定除防止了丢失修改,還可以進一步防止讀“髒”資料。但在二級封鎖協定中,由于讀完資料後即可釋放S鎖,是以它不能保證可重複讀。
3.3 三級加鎖協定
滿足一級加鎖協定,且事務在讀取資料之前必須先加S鎖,直到事務結束才釋放。
三級封鎖協定除防止了丢失修改和不讀“髒”資料外,還進一步防止了不可重複讀。
上述三級協定的主要差別在于什麼操作需要申請封鎖,以及何時釋放。
4. 兩段鎖協定(2-phase locking)
兩段鎖協定是指每個事務的執行可以分為兩個階段:生長階段(加鎖階段)和衰退階段(解鎖階段)。
加鎖階段:在該階段可以進行加鎖操作。在對任何資料進行讀操作之前要申請并獲得S鎖,在進行寫操作之前要申請并獲得X鎖。加鎖不成功,則事務進入等待狀态,直到加鎖成功才繼續執行。
解鎖階段:當事務釋放了一個封鎖以後,事務進入解鎖階段,在該階段隻能進行解鎖操作不能再進行加鎖操作。
若并發執行的所有事務均遵守兩段鎖協定,則對這些事務的任何并發排程政策都是可串行化的。
遵循兩段鎖協定的事務排程處理的結果是可串行化的充分條件,但是可串行化并不一定遵循兩段鎖協定。
兩段鎖協定和防止死鎖的一次封鎖法的異同之處,一次封鎖法要求每個事務必須一次将所有要使用的資料全部加鎖,否則就不能繼續執行,是以一次封鎖法遵守兩段鎖協定;但是兩段鎖協定并不要求事務必須一次将所有要使用的資料全部加鎖,是以遵守兩段鎖協定的事務可能發生死鎖。
五、不同的事務隔離級别與其對應可選擇的加鎖協定
事務隔離級别 | 加鎖協定 |
讀未送出 | 一級加鎖協定 |
讀已送出 | 二級加鎖協定 |
可重複讀 | 三級加鎖協定 |
串行化 | 兩段鎖協定 |
封鎖協定和隔離級别并不是嚴格對應的。