髒讀,不可重複讀,幻讀講解
阿新 • • 發佈:2020-10-10
- 首先我們先討論一下問題,是不是在ACID的保護下,資料就一定不會產生不一致的現象呢?
- 在關係資料庫庫系統中,多個會話可以訪問同一個資料庫的同一個表的同一行,這樣,對於資料而言,就意味著在同一個時間內,有多個會話可以對其施加操作(或讀操作或寫操作),讀寫操作施加的順序不停以及事務A特性對事務結果的影響(或成功或失敗,也就是要不提交要不中止)。這三種因素疊加在一起,會存在幾種對資料有不同影響的情況
- 讀-讀操作
- 如果同時只存在多個讀操作,對於資料沒有影響,也就是說讀-讀操作不影響資料的一致性,可以併發執行
- 讀-寫操作
- 如果讀寫操作都存在,寫在前讀在後(如髒讀現象),讀在前寫在後(如不可重複讀現象),或者讀在前寫在後然後再讀(如幻讀),就可能因為資料被寫而導致另外一個讀操作的會話讀到錯誤的資料。這個操作可以根據動作發生的先後順序被細分為讀-寫操作,寫-讀操作
- 寫-寫操作
- 如果同時存在多個寫操作,寫-寫操作直接改變了同一時刻的語義,這就更不允許,所以寫-寫操作通常不允許併發執行,但是如果不做併發控制,寫-寫操作也會帶來資料異常形象
- 讀-讀操作
- 這三種情況的第二種,對應的SQL標準中定義的三種資料異常,注意這三種異常主要是針對某個事物的讀操作而言的,我們看下SQL2003對於資料異常現象的定義
- 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
- 當在事務過程中,另一個事務向正在讀取的記錄新增或刪除新行時,就會發生幻讀。
- 請注意上面的標註,標註顯示了髒讀和不可重複讀是以a row(一行)為單位,而幻讀操作的是一個數據集合(零行到多行)
- 下面我們看一下四個隔離級別分別解決了什麼問題
- 下面我們開始案例
- 首先我們建立表,以及向表裡面填充一些資料
- 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條件)操作,而且不可重複讀和幻讀最大的區別是前者只需要“鎖住”已經讀過的資料,而幻讀需要對“不存在的資料”做出預防
參考書籍:資料庫事務處理的藝術 事務管理與併發控制
轉載於:https://blog.51cto.com/itzhoujun/2357093