?我是平也,這有一個專注Gopher技術成長的開源專案「go home」
背景介紹
想必事務大家都已經非常熟悉了,它是一組SQL組成的一個執行單元,要麼全執行要麼全不執行,這也是它的一個特性——原子性。而事務的應用場景也非常廣泛,最經典的就是轉賬問題,A給B打錢,不能出現A錢扣了B還沒收到的狀況,否則業務就亂套了。
事務的特性
於是呢,根據使用者對這些場景的嚴苛要求,總結出了事務應該具備的四個特性,分別是原子性、一致性、隔離性、永續性,簡稱事務的ACID屬性。
原子性
Atomicity,事務是一個最小的執行單位,事務裡面的SQL要麼全執行,要麼全不執行,就拿A與B轉賬為例,一條語句從A里扣錢,另一條語句往B身上加錢,如果這兩條語句不能全部執行,而是成功了一部分,那事務就沒有存在的意義了。
隔離性
Isolate,顧名思義就是將事務與另一個事務隔離開,為什麼要隔離呢?如果一個事務正在操作的資料被另一個事務修改或刪除了,最後的執行結果可能無法達到預期。如果沒有隔離性還會導致其他問題,稍後會有所說明。
永續性
Durable,意為事務完成了對資料的修改之後,修改的結果是永久性生效的。
一致性
Consistent,把一致性放在最後講的原因是前三個比較容易理解,而一致性的概念很模糊。
一致性是指事務使得應用系統從一個正確的狀態到另一個正確的狀態。
知乎上面一個高贊回答的很特別,原子性、隔離性、永續性是資料庫事務的基本特徵,而一致性是由AID這三個特徵來保證的。
那麼怎麼理解這句話呢?還拿轉賬為例,如果A手裡有100元,轉給B120元,顯然A手裡的錢不夠扣,假如你給金額這一列設定了不能小於0的約束,那麼在事務執行的時候監測到約束沒被滿足,就會回滾,這時可以說事務保證了一致性。同樣的,如果你沒有新增約束,而是在業務層做了校驗,並做了回滾,那麼也可以說事務保證了一致性。那如果資料庫和業務層都沒有做約束呢,A的錢不就變為負數了嗎?這實際上也是保證了一致性,因為執行前後並沒有破壞任何約束,它的狀態一直都是正確的。
事務併發帶來的問題
前面講到了事務的隔離性,如果要提升系統的吞吐量,當有多個任務需要處理時,應當讓多個事務同時執行,這就是事務的併發。既然事務存在併發執行,那必然產生同一個資料操作時的衝突問題,來看一下都會出現什麼問題。
更新丟失
Lost Update,當兩個事務更新同一行資料時,雙方都不知道對方的存在,就有可能覆蓋對方的修改。比如兩個人同時編輯一個文件,最後一個改完的人總會覆蓋掉前面那個人的改動。
髒讀
Dirty Reads,一個事務在執行時修改了某條資料,另一個事務正好也讀取了這條資料,並基於這條資料做了其他操作,因為前一個事務還沒提交,如果基於修改後的資料進一步處理,就會產生無法挽回的損失。
不可重複讀
Non-Repeatable Reads,同樣是兩個事務在操作同一資料,如果在事務開始時讀了某資料,這時候另一個事務修改了這條資料,等事務再去讀這條資料的時候發現已經變了,這就是沒辦法重複讀一條資料。
幻讀
Phantom Read,與上方場景相同,事務一開始按某個查詢條件沒查出任何資料,結果因為另一個事務的影響,再去查時卻查到了資料,這種就像產生幻覺了一樣,被稱作幻讀。
事務的四種隔離級別
首先,更新丟失這種問題應該是由應用層來解決的,因為資料庫沒有辦法控制使用者不去更新某條資料。但是另外三個問題是可以得到解決的,既然有方案解決解決它不就好了,幹嘛還要設定這麼多隔離級別呢?
剛才說了,如果我們要效能好、吞吐量提升,那就不得不付出一些代價,如果要做到完全沒有副作用,那麼就只需要讓事務排隊執行就好了,一個一個執行絕對不會出現髒讀幻讀的問題,但是這樣會導致資料庫處理的非常慢。那怎麼辦呢?官方唯一能做的就是給你提供各種級別的處理方式,由你根據具體業務場景選擇,於是就有了隔離級別。
讀未提交 Read uncommitted
讀未提交其實就是事務沒提交就可以讀,很顯然這種隔離級別會導致讀到別的還沒提交的資料,一旦基於讀到的資料做了進一步處理,而另一個事務最終回滾了操作,那麼資料就會錯亂,而且很難追蹤。總的來說說,讀未提交級別會導致髒讀。
讀提交 Read committed
顧名思義就是事務提交後才能讀,假設你拿著銀行卡去消費,付錢之前你看到卡里有2000元,這個時候你老婆在淘寶購物,趕在你前面完成了支付,這個時候你再支付的時候就提示餘額不足,但是分明你看到卡里的錢是夠的啊。
這就是兩個事務在執行時,事務A一開始讀取了卡里有2000元,這個時候事務B把卡里的錢花完了,事務A最終再確認餘額的時候發現卡里已經沒有錢了。很顯然,讀提交能解決髒讀問題,但是解決不了不可重複讀。
Sql Server,Oracle的預設隔離級別是Read committed。
可重複讀 Repeatable read
看名字就看出來了,它的出現就是為了解決不可重複讀問題,事務A一旦開始執行,無論事務B怎麼改資料,事務A永遠讀到的就是它剛開始讀的值。那麼問題就來了,假設事務B把id為1的資料改成了2,事務A並不知道id發生了變化,當事務A新增資料的時候卻發現為2的id已經存在了,這就是幻讀。
MySQL的預設隔離級別就是Repeatable read。
序列化 serializable
這個就是最無敵的存在了,所有的事務串起來一個個執行,因為沒有併發的場景出現了,什麼幻讀、髒讀、不可重複讀統統都不存在的。但是同樣的,基本併發能力會非常差。最終,到底什麼隔離級別完全要根據自己的業務場景選擇,沒有最好的,只有最適合的。
表格比較
隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
Read uncommitted | 是 | 是 | 是 |
Read committed | 否 | 是 | 是 |
Repeatable read | 否 | 否 | 是 |
serializable | 否 | 否 | 否 |
感謝大家的觀看,如果覺得文章對你有所幫助,歡迎關注公眾號「平也」,聚焦Go語言與技術原理。