臟讀,不可重復讀,幻讀講解
阿新 • • 發佈:2019-03-02
檢索 一行 images 關系 oss ces 事務提交 all eve 首先我們先討論一下問題,是不是在ACID的保護下,數據就一定不會產生不一致的現象呢?
在關系數據庫庫系統中,多個會話可以訪問同一個數據庫的同一個表的同一行,這樣,對於數據而言,就意味著在同一個時間內,有多個會話可以對其施加操作(或讀操作或寫操作),讀寫操作施加的順序不停以及事務A特性對事務結果的影響(或成功或失敗,也就是要不提交要不中止)。這三種因素疊加在一起,會存在幾種對數據有不同影響的情況
這三種情況的第二種,對應的SQL標準中定義的三種數據異常,註意這三種異常主要是針對某個事物的讀操作而言的,我們看下SQL2003對於數據異常現象的定義
請註意上面的標註,標註顯示了臟讀和不可重復讀是以a row(一行)為單位,而幻讀操作的是一個數據集合(零行到多行)
下面我們看一下四個隔離級別分別解決了什麽問題
下面我們開始案例
臟讀
*
不可重復讀
很多人不是很清除不可重復讀和幻讀的區別,下面我們解釋一下
- 讀-讀操作
- 如果同時只存在多個讀操作,對於數據沒有影響,也就是說讀-讀操作不影響數據的一致性,可以並發執行
- 讀-寫操作
- 如果讀寫操作都存在,寫在前讀在後(如臟讀現象),讀在前寫在後(如不可重復讀現象),或者讀在前寫在後然後再讀(如幻讀),就可能因為數據被寫而導致另外一個讀操作的會話讀到錯誤的數據。這個操作可以根據動作發生的先後順序被細分為讀-寫操作,寫-讀操作
- 寫-寫操作
- 如果同時存在多個寫操作,寫-寫操作直接改變了同一時刻的語義,這就更不允許,所以寫-寫操作通常不允許並發執行,但是如果不做並發控制,寫-寫操作也會帶來數據異常形象
- Dirty reads 臟讀
- A dirty read (aka uncommitted dependency) occurs when a transaction is allowed to read data from
a row
that has been modified by another running transaction and not yet committed. - 當一個事務被允許從另一個正在運行的事務修改而尚未提交的行中讀取數據時,就會發生臟讀
- Non-repeatable reads 不可重復讀
- A non-repeatable read occurs, when during the course of a transaction,
a row
is retrieved twice and the values within the row differ between reads. - 不可重復讀取發生在事務過程中,當一行被檢索兩次,且該行中的某些值在讀取之間有不同時。
- Phantom reads 幻讀
- A phantom read occurs when, in the course of a transaction,
new rows
- 當在事務過程中,另一個事務向正在讀取的記錄添加或刪除新行時,就會發生幻讀。
- 首先我們創建表,以及向表裏面填充一些數據
- Create Table: CREATE TABLE
goods
( id
int(11) NOT NULL AUTO_INCREMENT,title
varchar(32) NOT NULL DEFAULT ‘‘ COMMENT ‘商品名稱‘,classify
tinyint(4) NOT NULL DEFAULT ‘0‘ COMMENT ‘商品類型‘,- PRIMARY KEY (
id
), - KEY
idx_classify
(classify
) - ) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8
- mysql> select * from goods;
- +----+----------+----------+
- | id | title | classify |
- +----+----------+----------+
- | 1 | 商品1 | 1 |
- | 2 | 商品2 | 3 |
- | 3 | 商品3 | 5 |
- | 4 | 商品4 | 8 |
- | 5 | 商品5 | 10 |
- | 6 | 商品6 | 1 |
- | 7 | 商品7 | 3 |
- | 8 | 商品8 | 5 |
- | 9 | 商品9 | 8 |
- | 10 | 商品10 | 10 |
- +----+----------+----------+
- 10 rows in set (0.00 sec)
*
- 我們可以看到T1執行step 5的時候,已經讀取到了事務T2未提交的修改的行,這個時候就發生了臟讀
- 在上圖中,我們可以看到,T2執行了step 4,並且在step 5上看到了最後修改的數據,然後T1執行了step6 ,這時候讀取到的還是舊值,因為T2還沒有提交,那麽這就解決了臟讀問題,然後T2執行step 7提交,T1執行step8,這時候因為T2 已經提交了,所以看到了最新的值,那麽這時候就發生了不可重復讀問題
幻讀
- 雖然T2在step 7提交了, 但T1在step8 查出來的title還是商品2,這說明解決了不可重復讀問題,下面我們再看一下幻讀問題是如何出現的
- 我們可以看到T1在step 3的時候查出來是4條數據,接著T2執行了step 4,5,6 ,在這個過程,classify=3的數據是有5條的,接著T1執行了step7 看到了還是4條,接著執行了T1的step8,這時候T1的step9就看到5條了,就發生了幻讀問題,幻讀指的是執行同樣的sql,第二次會返回之前不存在的行或者之前的行不見了,這就叫做幻讀
- 那麽如何解決幻讀問題呢,有兩種方法
- 將隔離級別改為 SERIALIZABLE
- 隔離級別為 REPEATABLE READ,但是加上鎖定讀
- 方案1:
- 我們可以看到T2在step 4的時候就執行不下去,因為T1在step3的時候已經加了共享鎖,然後T2在step4又去申請排他鎖,因為T1沒有釋放classify=3行的鎖,所以T2的step4最終因為鎖等待超時而報錯,那麽在這種加鎖的機制下,也就不存在幻讀問題了
- 方案2
- 我們可以看到T2在step 4的時候就執行不下去,因為T1在step3的時候已經加了共享鎖,然後T2在step4又去申請排他鎖,因為T1沒有釋放classify=3行的鎖,所以T2的step4最終因為鎖等待超時而報錯,那麽在這種加鎖的機制下,也就不存在幻讀問題了
- SELECT ... LOCK IN SHARE MODE
- 在讀取出來的記錄上面加上一個共享鎖,其他會話也可以讀取這些記錄,但在你的事務提交之前其他事務是不能修改這些記錄的,如果這些行中的任何一行已被另一個尚未提交的事務更改,那麽您的查詢將一直等到該事務結束,然後使用最新值。
- SELECT ... FOR UPDATE
- 對於搜索查到的索引記錄,鎖定行和任何關聯索引項,就像你為這些行發送了update語句一樣,其他事務如果修改這些行將會被阻塞,例如執行SELECT ... LOCK IN SHARE MODE操作或者從某些事務隔離級別讀取數據,一致讀取忽略設置在讀視圖中的記錄上的任何鎖(老版本記錄無法被鎖定,它們通過在記錄的內存副本中撤銷日誌來重建。)
- 首先,這兩種異常對於T1來說,都是先讀取了數據,之後因為T2“寫”了數據而導致T1再次讀取數據的時候出現了異常,但是對應不可重復讀來說,T1讀取的是一個存在的確定的一行數據,這個行數據被T1使用刪除或者更新操作而改變,而幻讀對應T1讀取的是滿足條件的多行數據,意味著這是一個範圍找到,數據集是不確定的,所以從第一次讀取數據的操作的角度看,前者是讀取特定的行,後者讀取的是多行,一行,或者零行,其次這兩種異常,對於T2來說,都是’寫’數據,但是寫操作的具體動作不同,不可重復讀對T2的寫操作是更新或者刪除操作,而幻讀對於T2的寫操作是插入(插入的新條件滿足where條件)或更新(使不滿足where條件的數據在更新後滿足where條件)操作,而且不可重復讀和幻讀最大的區別是前者只需要“鎖住”已經讀過的數據,而幻讀需要對“不存在的數據”做出預防
參考書籍:數據庫事務處理的藝術 事務管理與並發控制
臟讀,不可重復讀,幻讀講解