不可不知的數據庫的讀現象淺析
“讀現象”是多個事務并發執行時,在讀取數據方面可能碰到的狀況。先了解它們有助于理解各隔離級別的含義。其中包括臟讀、不可重復讀和幻讀。
臟讀
臟讀又稱無效數據的讀出,是指在數據庫訪問中,事務T1將某一值修改,然后事務T2讀取該值,此后T1因為某種原因撤銷對該值的修改,這就導致了T2所讀取到的數據是無效的。
臟讀就是指當一個事務正在訪問數據,并且對數據進行了修改,而這種修改還沒有提交(commit)到數據庫中,這時,另外一個事務也訪問這個數據,然后使用了這個數據。因為這個數據是還沒有提交的數據,那么另外一個事務讀到的這個數據是臟數據,依據臟數據所做的操作可能是不正確的。
舉例說明:
在下面的例子中,事務2修改了一行,但是沒有提交,事務1讀了這個沒有提交的數據。現在如果事務2回滾了剛才的修改或者做了另外的修改的話,事務1中查到的數據就是不正確的了。
事務一 | 事務二 |
---|---|
/* Query 1 */ SELECT age FROM users WHERE id = 1; /* will read 20 */ |
|
/* Query 2 */ UPDATE users SET age = 21 WHERE id = 1; /* No commit here */ |
|
/* Query 1 */ SELECT age FROM users WHERE id = 1; /* will read 21 */ |
|
ROLLBACK; /* lock-based DIRTY READ */ |
在這個例子中,事務2回滾后就沒有id是1,age是21的數據了。所以,事務一讀到了一條臟數據。
不可重復讀
不可重復讀,是指在數據庫訪問中,一個事務范圍內兩個相同的查詢卻返回了不同數據。這是由于查詢時系統中其他事務修改的提交而引起的。比如事務T1讀取某一數據,事務T2讀取并修改了該數據,T1為了對讀取值進行檢驗而再次讀取該數據,便得到了不同的結果。
一種更易理解的說法是:在一個事務內,多次讀同一個數據。在這個事務還沒有結束時,另一個事務也訪問該同一數據。那么,在第一個事務的兩次讀數據之間。由于第二個事務的修改,那么第一個事務讀到的數據可能不一樣,這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱為不可重復讀,即原始讀取不可重復。
舉例說明:
在基于鎖的并發控制中“不可重復讀(non-repeatable read)”現象發生在當執行SELECT 操作時沒有獲得讀鎖(read locks)或者SELECT操作執行完后馬上釋放了讀鎖; 多版本并發控制中當沒有要求一個提交沖突的事務回滾也會發生“不可重復讀(non-repeatable read)”現象。
事務一 | 事務二 |
---|---|
/* Query 1 */ SELECT * FROM users WHERE id = 1; |
|
/* Query 2 */ UPDATE users SET age = 21 WHERE id = 1; COMMIT; /* in multiversion concurrency control, or lock-based READ COMMITTED */ |
|
/* Query 1 */ SELECT * FROM users WHERE id = 1; COMMIT; /*lock-based REPEATABLE READ */ |
在這個例子中,事務2提交成功,因此他對id為1的行的修改就對其他事務可見了。但是事務1在此前已經從這行讀到了另外一個“age”的值。
幻讀
幻讀是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,比如這種修改涉及到表中的“全部數據行”。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入“一行新數據”。那么,以后就會發生操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺一樣.一般解決幻讀的方法是增加范圍鎖RangeS,鎖定檢鎖范圍為只讀,這樣就避免了幻讀。
幻讀(phantom read)”是不可重復讀(Non-repeatable reads)的一種特殊場景:當事務沒有獲取范圍鎖的情況下執行SELECT … WHERE操作可能會發生“幻影讀(phantom read)”。
舉例說明:
當事務1兩次執行SELECT … WHERE檢索一定范圍內數據的操作中間,事務2在這個表中創建了(如INSERT)了一行新數據,這條新數據正好滿足事務1的“WHERE”子句。
事務一 | 事務二 |
---|---|
/* Query 1 */ SELECT * FROM users WHERE age BETWEEN 10 AND 30; |
|
/* Query 2 */ INSERT INTO users VALUES ( 3, 'Bob', 27 ); COMMIT; |
|
/* Query 1 */ SELECT * FROM users WHERE age BETWEEN 10 AND 30; |
在這個例子中,事務一執行了兩次相同的查詢操作。但是兩次操作中間事務二向數據庫中增加了一條符合事務一的查詢條件的數據,導致幻讀。
參考資料
打賞