1. 程式人生 > >高階資料庫五:淺談資料庫隔離級別與鎖機制

高階資料庫五:淺談資料庫隔離級別與鎖機制

因為資料庫中的事務是具有隔離性的,一個事務的執行不應該影響另一個事務的執行。
但是因為並行機制的存在,會有一系列的問題:

髒讀:事務A修改了一個數據,但未提交,事務B讀到了事務A未提交的更新結果,如果事務A提交失敗,事務B讀到的就是髒資料。
不可重複讀:在同一個事務中,對於同一份資料讀取到的結果不一致。比如,事務B在事務A提交前讀到的結果,和提交後讀到的結果可能不同。
幻讀:在同一個事務中,同一個查詢多次返回的結果不一致。通常是因為在事務A中進行了一次全域性操作,如新增了一條記錄;事務B在事務A提交前後各執行了一次查詢操作,發現後一次比前一次多了一條記錄。

所以我們要有一定的隔離機制來滿足資料庫並行速度和操作正確性的需求。

隔離級別

可讀取未確認(Read uncommitted)

寫事務阻止其他寫事務,避免了更新遺失。但是沒有阻止其他讀事務。

存在的問題:髒讀。即讀取到不正確的資料,因為另一個事務可能還沒提交最終資料,這個讀事務就讀取了中途的資料,這個資料可能是不正確的。

解決辦法就是下面的“可讀取確認”。

可讀取確認(Read committed)

寫事務會阻止其他讀寫事務。讀事務不會阻止其他任何事務。

大部分資料庫使用的預設隔離級別,兼顧速度和正確性。

存在的問題:不可重複讀。即在一次事務之間,進行了兩次讀取,但是結果不一樣,可能第一次id為1的人叫“李三”,第二次讀id為1的人就叫了“李四”。因為讀取操作不會阻止其他事務。

解決辦法就是下面的“可重複讀”。

可重複讀(Repeatable read)

讀事務會阻止其他寫事務,但是不會阻止其他讀事務。

存在的問題:幻讀。可重複讀阻止的寫事務包括update和delete(只給存在的表加上了鎖),但是不包括insert(新行不存在,所以沒有辦法加鎖),所以一個事務第一次讀取可能讀取到了10條記錄,但是第二次可能讀取到11條,這就是幻讀。

解決辦法就是下面的“序列化”。

可序列化(Serializable)

讀加共享鎖,寫加排他鎖。這樣讀取事務可以併發,但是讀寫,寫寫事務之間都是互斥的,基本上就是一個個執行事務,所以叫序列化。

遊標穩定性(CS-Cursor Stability)

這種隔離級別已經基本被淘汰了。

遊標穩定性隔離級別只鎖定事務宣告和當前所引用的行。

遊標穩定性隔離級別在隔離事務效果方面非常寬鬆,介於RC和RR之間。它可以防止髒讀,但有可能出現不可重複讀和幻像讀。

在一個事務中,只有正在被讀取的那一行(遊標指向的行)將被加上NS鎖,其他未被處理的行上不被加鎖。這種隔離級只能保證正在被處理的行的值不會被其他併發的程式所改變。

所獲取的鎖一直有效,直到遊標重定位或事務終止為止,如果遊標重定位,原來行上的鎖就被釋放,並獲得遊標現在所引用行上的鎖。

此外,如果事務修改了它檢索到的任何行,那麼在事務終止之前,其他事務不能更新或刪除該行,即使遊標不再位於被修改的行。

與可重複讀和讀穩定性隔離級別一樣,其他事務在其他行上進行的更改,在這些更改提交之前對於使用遊標穩定性隔離級別(這是預設的隔離級別)的事務是不可見的。

如果如家酒店客房預定程式在遊標穩定性隔離級別下執行,那麼會有什麼影響呢?

當一個顧客檢索某個日期段內所有可用房間的列表,然後檢視產生的列表上每個房間的資訊時(每次檢視一個房間),您可以更改旅館中任何房間的房價,除了該顧客當前正在檢視的房間外(對於指定的日期段)。

同樣,其他顧客可以對任何房間進行或取消預訂,但是這個顧客當前正在檢視的房間除外(對於指定的日期段)。也就是說,對於第一個顧客當前正在檢視的房間記錄,您和其他顧客都不能進行任何操作。當第一個顧客檢視列表中另一個房間的資訊時,您和其他顧客就可以修改他剛才檢視的房間記錄(如果這個顧客沒有預訂這個房間的話)。

能夠解決的問題是

圖1

如圖所示,在事務2修改A之後,事務1再修改A,則會導致事務二的修改無效,從而丟失資訊。所以在事務1讀取A的時候就給他加一個cursor lock,直到修改完成並不再需要任何關於A的操作或者COMMIT之後。這樣就有可能解決這個問題。

快照隔離(Snapshot Isolation)

這種隔離級別可以保證一個事務中的讀操作所讀到的東西都是事務開始時的那樣。如果事務的寫入不與該快照進行的讀取有任何併發更新衝突,則事務將在SI下進行提交。

這樣會導致WRITE SKEW ANOMALY問題。

圖2

兩個SI級別的修改同時發生,他們看到的景象是相同的,他們做出的修改也是不同行的。所以他們的修改可以成功。

但是理論上修改操作效果應該如下圖所示:

圖3

主流資料庫隔離級別

Database Default Isolation Maximum Isolation
Actian Ingres 10.0/10S S S
Aerospike RC RC
Akiban Persistit SI SI
Clustrix CLX 4100 RR ?
Greenplum 4.1 RC S
IBM DB2 10 for z/OS CS S
IBM Informix 11.50 Depends RR
MySQL 5.6 RR S
MemSQL 1b RC RC
MS SQL Server 2012 RC S
NuoDB CR CR
Oracle 11g RC SI
Oracle Berkeley DB S S
Oracle Berkeley DB JE RR S
Postgres 9.2.2 RC S
SAP HANA RC SI
ScaleDB 1.02 RC RC
VoltDB S S
Legend RC: read committed
RR: repeatable read
S: serializability
SI: snapshot isolation
CS: cursor stability
CR: consistent read

鎖機制

一次性鎖協議

事務開始時,即一次性申請所有的鎖,之後不會再申請任何鎖,如果其中某個鎖不可用,則整個申請就不成功,事務就不會執行,在事務尾端,一次性釋放所有的鎖。一次性鎖協議不會產生死鎖的問題,但事務的併發度不高。

兩階段鎖協議

整個事務分為兩個階段,前一個階段為加鎖,後一個階段為解鎖。在加鎖階段,事務只能加鎖,也可以操作資料,但不能解鎖,直到事務釋放第一個鎖,就進入解鎖階段,此過程中事務只能解鎖,也可以操作資料,不能再加鎖。

兩階段鎖協議使得事務具有較高的併發度,因為解鎖不必發生在事務結尾。它的不足是沒有解決死鎖的問題,因為它在加鎖階段沒有順序要求。如兩個事務分別申請了A, B鎖,接著又申請對方的鎖,此時進入死鎖狀態,如下圖所示:

圖4

時間戳排序協議

每個事務都有一個唯一的時間戳,也就是其進入系統的時間。

時間戳有大小之分,如果事務Ti比Tj先進入系統,則TS(Ti)

參考文獻