數據庫事務的四大特性(ACID)
原子性(Atomic)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)
數據庫系統支持事務模式
1、自動提交模式
自動提交模式是每個sql語句都是一個獨立的事務,當數據庫系統執行完一個sql語句後,會自動提交事務
2、手工提交模式必須由數據庫的客戶程序顯示指定事務開始和結束邊界
數據庫併發問題
1.髒讀(dirty read)
A事務讀取了B事務尚未提交的更改數據,並且在這個數據基礎上進行操作。如果此時恰巧B事務進行回滾,那麼A事務讀到的數據是根本不被承認的。
以下是一個取款事務和轉賬事務併發時引起的髒讀場景。
時間 | 轉賬事務A | 取款事務B |
T1 | 開始事務 | |
T2 | 開始事務 | |
T3 | 查詢賬戶餘額爲1000元 | |
T4 | 取出500元,把餘額改爲500元 | |
T5 | 查詢賬戶餘額爲500元(髒讀) | |
T6 | 撤銷事務,餘額恢復爲1000元 | |
T7 | 匯入100元,餘額改爲600元 | |
T8 | 提交事務 |
在這個場景中,B希望取款500元,而後有撤銷了動作,而A往同一個賬戶轉賬100元,因爲A事務讀取了B事務尚未提交的數據,因而導致了賬戶白白丟失了500元。在Oracle數據中,不會發生髒讀的情況。
2.不可重複讀(unrepeatable read)
不可重複讀是指A事務讀取了B事務已經提交的更改數據。假設A在取款事務的過程中,B往該賬戶轉賬100元,A兩次讀取賬戶的餘額發生不一致
時間 | 取款事務A | 轉賬事務B |
T1 | 開始事務 | |
T2 | 開始事務 | |
T3 | 查詢賬戶餘額爲1000元 | |
T4 | 查詢賬戶餘額爲1000元 | |
T5 | 取出100元,把餘額改爲900元 | |
T6 | 提交事務 | |
T7 | 查詢賬戶餘額爲900元 |
在同一個事務中T4和T7時間點讀取的賬戶存款餘額不一致
3.幻象讀(phantom read)
A事務讀取B提交的新增數據,這時A事務將出現幻想讀的問題。幻讀一般發生在計算統計數據的事務中。舉個例子,假設銀行系統在同一個事務中兩次統計存款的總金額,在兩次統計過程中,剛好新增了一個存款賬戶,並存入100元,這時兩次統計的總金額將不一致。
時間 | 統計金額事務A | 轉賬事務B |
T1 | 開始事務 | |
T2 | 開始事務 | |
T3 | 統計存款總金額爲10000元 | |
T4 | 新增一個存款賬戶,存款爲100元 | |
T5 | 提交事務 | |
T6 | 再次統計存款總金額爲10100元(幻象讀) |
如果新增的數據剛好滿足事務的查詢條件,那麼這個新數據就會進入事務的視野,因而導致兩次統計結果不一致的情況。
幻讀和不可重複讀是兩個容易混淆的概念,
幻讀是指讀到了其他事物已經提交的新增數據。策略:添加一個表級鎖–將整張表鎖定
不可重複讀是讀到了已經提交事務的更改數據(更改或刪除)。策略:對操作的數據添加行級鎖
4.第一類丟失更新
A事務撤銷時,把已經提交的B事務的更新數據覆蓋了。這種錯可能會造成很嚴重的問題。通過下面的賬號取款轉賬就可以看出來。
時間 | 取款事務A | 轉賬事務B |
T1 | 開始事務 | |
T2 | 開始事務 | |
T3 | 查詢賬號餘額爲1000元 | |
T4 | 查詢餘額爲1000元 | |
T5 | 匯入100元,把餘額改爲1100元 | |
T6 | 提交事務 | |
T7 | 取出100元,把餘額改爲900元 | |
T8 | 撤銷事務 | |
T9 | 餘額恢復爲1000元(丟失更新) |
A事務在撤銷時,“不小心”將B事務已經轉入賬號的金額給抹去了。
5.第二類丟失更新
A事務覆蓋B事務已經提交的數據,造成B事務所操作丟失。
時間 | 轉賬事務A | 取款事務B |
T1 | 開始事務 | |
T2 | 開始事務 | |
T3 | 查詢賬號餘額爲1000元 | |
T4 | 查詢餘額爲1000元 | |
T5 | 取出100元,把餘額改爲900元 | |
T6 | 提交事務 | |
T7 | 匯入100元,把餘額改爲1100元 | |
T8 | 提交事務 | |
T9 | 把餘額改爲1100元(丟失更新) |
在上面的例子,由於支票轉賬事務覆蓋了取款事務對存款餘額所做的更新,導致銀行最後損失了100元,相反如果轉賬事務先提交,那麼用戶損失了100元
數據庫的鎖
悲觀鎖:數據庫總是認爲多個數據庫併發操作會發生衝突,所以總是要求加鎖操作。悲觀鎖主要表鎖、行鎖、頁鎖。
樂觀鎖:
數據庫總是認爲多個數據庫併發操作不會發生衝突,所以總是不加鎖操作。所以在數據進行提交更新的時候,纔會正式對數據的衝突與否進行檢測,如果發現衝突了,則讓返回用戶錯誤的信息,讓用戶決定如何去做。樂觀鎖的實現方式一般包括使用版本號和時間戳。
表級鎖:讀鎖鎖表,會阻礙其他事務修改表數據。寫鎖鎖表會阻礙其他事務讀與寫。
頁級鎖:就是對頁加鎖
行級鎖: 共享鎖:一個事務對一行的共享只讀鎖。排它鎖:一個事務對一行的排他讀寫鎖。
共享鎖:
加鎖與解鎖:當一個事務執行select語句時,數據庫系統會爲這個事務分配一把共享鎖,來鎖定被查詢的數據。在默認情況下,數據被讀取後,數據庫系統立即解除共享鎖。例如,當一個事務執行查詢“SELECT * FROM accounts”語句時,數據庫系統首先鎖定第一行,讀取之後,解除對第一行的鎖定,然後鎖定第二行。這樣,在一個事務讀操作過程中,允許其他事務同時更新 accounts表中未鎖定的行。
兼容性:如果數據資源上放置了共享鎖,還能再放置共享鎖和更新鎖。
併發性能:具有良好的併發性能,當數據被放置共享鎖後,還可以再放置共享鎖或更新鎖。所以併發性能很好。
排它鎖:
加鎖與解鎖:當一個事務執行insert、update或delete語句時,數據庫系統會自動對SQL語句操縱的數據資源使用獨佔鎖。如果該數據資源已經有其他鎖(任何鎖)存在時,就無法對其再放置獨佔鎖了。
兼容性:獨佔鎖不能和其他鎖兼容,如果數據資源上已經加了獨佔鎖,就不能再放置其他的鎖了。同樣,如果數據資源上已經放置了其他鎖,那麼也就不能再放置獨佔鎖了。
併發性能:最差。只允許一個事務訪問鎖定的數據,如果其他事務也需要訪問該數據,就必須等待。
更新鎖:
加鎖與解鎖:當一個事務執行update語句時,數據庫系統會先爲事務分配一把更新鎖。當讀取數據完畢,執行更新操作時,會把更新鎖升級爲獨佔鎖。
兼容性:更新鎖與共享鎖是兼容的,也就是說,一個資源可以同時放置更新鎖和共享鎖,但是最多放置一把更新鎖。這樣,當多個事務更新相同的數據時,只有一個事務能獲得更新鎖,然後再把更新鎖升級爲獨佔鎖,其他事務必須等到前一個事務結束後,才能獲取得更新鎖,這就避免了死鎖。
併發性能:允許多個事務同時讀鎖定的資源,但不允許其他事務修改它。
意向鎖(意向共享,意向更新):
在判斷每一行是否已經被行鎖鎖定效率比較低下,因此使用意向鎖,當發現表上有意向共享鎖,說明表中有些行被共享行鎖鎖住了,因此,事務B申請表的寫鎖會被阻塞。
事務隔離級別
Read uncommitted 一個事務可以讀取另一個未提交事務的數據 。 缺點:會產生髒讀、不可重複讀、幻讀。
Read committed 一個事務要等另一個事務提交後才能讀取數據。 缺點:會產生不可重複讀、幻讀。 Oracle默認隔離級別
Repeatable read 重複讀就是在開始讀取數據時,不再允許修改操作。 缺點:會產生幻讀。 MySQL默認的隔離級別。
Serializable 序列化 最高的事務隔離級別,在該級別下,事務串行化順序執行。
缺點:可以解決併發事務的所有問題。但是效率地下,消耗數據庫性能,一般不使用。
事務的七種傳播行爲
REQUIRED:如果當前沒有事務,就創建一個新事務,如果當前存在事務,就加入該事務,該設置是最常用的設置。
SUPPORTS:支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就以非事務執行。‘
MANDATORY:支持當前事務,如果當前存在事務,就加入該事務,如果當前不存在事務,就拋出異常。
REQUIRES_NEW:創建新事務,無論當前存不存在事務,都創建新事務。
NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
NEVER:以非事務方式執行,如果當前存在事務,則拋出異常。
NESTED:如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與REQUIRED類似的操作。
參考文章: