讓我們一起了解事務(wù)之ACID
本文轉(zhuǎn)載自微信公眾號(hào)「程序員阿sir」,作者程序員阿sir。轉(zhuǎn)載本文請(qǐng)聯(lián)系程序員阿sir公眾號(hào)。
隨著科技的飛速發(fā)展,人類社會(huì)也邁入了大數(shù)據(jù)時(shí)代。很多數(shù)據(jù)的值不準(zhǔn)對(duì)我們的生活影響不會(huì)很大,比如手表記錄下來(lái)的我今天走路步數(shù)是10000步,實(shí)際就算記成了9500步對(duì)我來(lái)講也不會(huì)太關(guān)心。但是有些數(shù)據(jù)就分毫不能差,比如銀行卡里的錢不能莫名其妙的就少了500塊錢。所有的數(shù)據(jù)肯定都需要存在在一些數(shù)據(jù)系統(tǒng)里面,比如數(shù)據(jù)庫(kù),硬盤,云存儲(chǔ)等等。但是現(xiàn)在的應(yīng)用越來(lái)越復(fù)雜,在訪問(wèn)數(shù)據(jù)系統(tǒng)經(jīng)常出現(xiàn)各種問(wèn)題,比如:
- 寫數(shù)據(jù)到數(shù)據(jù)庫(kù)的時(shí)候出現(xiàn)了軟件或硬件故障導(dǎo)致數(shù)據(jù)寫了一半失敗了;
- 應(yīng)用突然崩了;
- 網(wǎng)絡(luò)突然斷了導(dǎo)致應(yīng)用連接不上數(shù)據(jù)庫(kù)了;
- 多個(gè)客戶端想要同時(shí)更新同一個(gè)值導(dǎo)致后者覆蓋了前者。
比如小明想給小華轉(zhuǎn)賬100塊錢,銀行應(yīng)用的業(yè)務(wù)邏輯是如果小明賬號(hào)里的余額大于100元時(shí),會(huì)從小明的賬號(hào)里減去100元,然后把小華的賬號(hào)余額加上100元。但是如果剛從小明的賬號(hào)里扣除100元時(shí),銀行網(wǎng)斷了導(dǎo)致后面給小華的錢沒(méi)加成功,這樣就出現(xiàn)了重大問(wèn)題,在銀行轉(zhuǎn)賬時(shí)絕對(duì)不能發(fā)生。所以銀行的應(yīng)用開(kāi)發(fā)時(shí)需要考慮這種異常并進(jìn)行處理,比如等來(lái)網(wǎng)了的時(shí)候把小華的賬號(hào)里加上100塊錢。
我們可以在應(yīng)用端處理各種異常,讓應(yīng)用變得更加魯棒,但是其中這里面有很多細(xì)節(jié)的問(wèn)題需要考慮,非常麻煩。而事務(wù) ( Transaction ) 是一種可以簡(jiǎn)化問(wèn)題的機(jī)制。
1. 什么是事務(wù)
事務(wù)可以把一個(gè)應(yīng)用的多個(gè)讀寫操作合并為一個(gè)邏輯單元。理論來(lái)說(shuō),一個(gè)事務(wù)中的所有讀寫操作執(zhí)行的時(shí)候就像是一個(gè)操作一樣:或者整個(gè)事務(wù)的所有操作成功,或者整個(gè)事務(wù)的所有操作全部失敗。 不允許出現(xiàn)在一個(gè)事務(wù)中:其中的一部分操作成功了,但是另一部分的操作失敗了的情況。這樣就簡(jiǎn)化了問(wèn)題。比如上面的例子中,把扣除小明賬號(hào)的錢和增加小華賬號(hào)里的錢看作一個(gè)事務(wù),那中間網(wǎng)斷了最多就是沒(méi)轉(zhuǎn)帳成功,兩邊的錢都沒(méi)變化,這就保證了不會(huì)存在錢轉(zhuǎn)丟了的情況。這樣也方便應(yīng)用進(jìn)行重試 ( Safely Retry )。
大家可能之前都聽(tīng)說(shuō)過(guò)或用到過(guò)事務(wù),看上去好像數(shù)據(jù)庫(kù)就應(yīng)該使用事務(wù)。但是事務(wù)并不是本來(lái)就天然存在的,他是為了簡(jiǎn)化訪問(wèn)數(shù)據(jù)庫(kù)時(shí)的編程模型而被創(chuàng)造出來(lái)的概念。使用事務(wù)可以幫助開(kāi)發(fā)者忽略一些潛在的數(shù)據(jù)問(wèn)題和并發(fā)問(wèn)題,因?yàn)閷?shí)現(xiàn)事務(wù)的數(shù)據(jù)庫(kù)本身會(huì)幫忙處理這類問(wèn)題。
那既然事務(wù)這么好,是不是所有數(shù)據(jù)庫(kù)都實(shí)現(xiàn)了事務(wù),我們就直接用就行,不需要了解事務(wù)的具體原理了。實(shí)際不是的。因?yàn)槭聞?wù)的實(shí)現(xiàn)有很多種方式以及不同的級(jí)別,他們對(duì)應(yīng)用性能的影響也是不同的。比如一種事務(wù)隔離性的實(shí)現(xiàn)方式就是加鎖,我們把一個(gè)事務(wù)里涉及的所有數(shù)據(jù)行都加上鎖,這樣別的事務(wù)根本不能讀寫我們鎖下的這些行數(shù)據(jù),直到事務(wù)結(jié)束才釋放這些鎖,這樣肯定就能避免數(shù)據(jù)不一致的情況了。但是這樣相當(dāng)于把讀寫數(shù)據(jù)串行化了,會(huì)非常影響性能。銀行數(shù)據(jù)非常重要,這樣實(shí)現(xiàn)雖然慢但是保證數(shù)據(jù)的安全了也還能接受。但是假設(shè)我們的應(yīng)用中數(shù)據(jù)沒(méi)有那么重要的情況下,可能我們這種拿性能換數(shù)據(jù)一致的做法就不太合理了。因此不是每個(gè)應(yīng)用都需要事務(wù)。
盡管事務(wù)看上去簡(jiǎn)單直接,但是實(shí)際有很多細(xì)節(jié)需要考慮情況,我們將會(huì)一一進(jìn)行介紹。首先先介紹一下數(shù)據(jù)庫(kù)中 ACID 的概念。
2. 什么是 ACID
事務(wù)提供的安全保證 ( Safety Guarantee )可以用 ACID 來(lái)描述分別是:
- 原子性 ( Atomicity )
- 一致性 ( Consistency )
- 隔離性 ( Isolation)
- 持久性 (Durability )。
這是一些事務(wù)的安全保證,但是不同數(shù)據(jù)庫(kù)對(duì)于ACID的實(shí)現(xiàn)可能也是不同的。比如對(duì)于隔離性就存在巨大的歧義。所以今天一個(gè)數(shù)據(jù)庫(kù)說(shuō)自己滿足ACID要求,但是可能和你以為的ACID并不一樣。下面分別對(duì)這四種安全保證進(jìn)行解釋。
2.1. 原子性 ( Atomicity )
原子這個(gè)詞很容易造成誤解。大家很容易聯(lián)想到多線程中的原子操作。如果一個(gè)線程執(zhí)行原子操作,表示其他線程不能看到這個(gè)原子操作的中間結(jié)果。
但是在 ACID 中,原子性和并發(fā)無(wú)關(guān),也就是說(shuō) ACID 中的原子性不表示當(dāng)多個(gè)進(jìn)程想要在同一時(shí)刻訪問(wèn)同一數(shù)據(jù)時(shí)會(huì)發(fā)生什么,但是隔離性表示的是這個(gè)意思。
ACID 中的原子性描述了如果一個(gè)事務(wù)中間出現(xiàn)了錯(cuò)誤(比如網(wǎng)絡(luò)突然斷了)導(dǎo)致這個(gè)事務(wù)不可能完成,那么事務(wù)必須回滾到事務(wù)開(kāi)始之前的狀態(tài)。 也就是說(shuō)刪掉這個(gè)事務(wù)已經(jīng)寫到數(shù)據(jù)庫(kù)中的修改。
原子性保證了事務(wù)中的操作要么全都發(fā)生,要么全都不發(fā)生,不可能一半完成了,一半沒(méi)做。其實(shí)回滾性 (Abortability) 這個(gè)名字比原子性更能表示這個(gè)特征,但是原子性還是更通用的一個(gè)叫法。
舉例來(lái)說(shuō),小明要給小華轉(zhuǎn)賬100元。事務(wù)里包含兩個(gè)操作,第一個(gè)是從小明的賬號(hào)里扣除100元,第二個(gè)是在小華的賬號(hào)里增加100元。原子性保證這個(gè)事務(wù)要么操作全成功,要么操作全失敗,不能出現(xiàn)小明的賬戶里少了100元,小華的賬號(hào)里錢數(shù)沒(méi)變。
2.2. 一致性 (Consistency)
ACID 中的一致性限制了我們自定義的一些數(shù)據(jù)約束永遠(yuǎn)為真。 比如上面銀行轉(zhuǎn)賬的例子,銀行定義的一個(gè)約束條件是無(wú)論怎么轉(zhuǎn)賬,大家的存款總額不變。
但是一致性實(shí)際取決于應(yīng)用本身的定義,也就是說(shuō)應(yīng)用負(fù)責(zé)定義哪些東西需要保持一致性。比如應(yīng)用邏輯就是要求扣小明100然后給小華賬號(hào)加200塊,數(shù)據(jù)庫(kù)也不能阻止他這樣做,因?yàn)檫@個(gè)是應(yīng)用里面定義的。數(shù)據(jù)庫(kù)可能可以加一些外鍵約束或者唯一性約束,但是一般來(lái)講,應(yīng)用負(fù)責(zé)定義什么數(shù)據(jù)是合理的,什么是不合理的,數(shù)據(jù)庫(kù)只負(fù)責(zé)存儲(chǔ)。
有意思的一點(diǎn)是:原子性、隔離性、持久性都是數(shù)據(jù)庫(kù)的性質(zhì),而一致性是應(yīng)用的性質(zhì)。應(yīng)用需要依數(shù)據(jù)庫(kù)的原子性和隔離性來(lái)實(shí)現(xiàn)一致性。所以從某種意義來(lái)說(shuō),ACID 中的 C 不應(yīng)該屬于數(shù)據(jù)庫(kù)的安全性保證范疇。
2.3. 隔離性
一個(gè)數(shù)據(jù)庫(kù)可以被多個(gè)客戶端訪問(wèn),如果他們想要讀寫數(shù)據(jù)庫(kù)的不同部分肯定沒(méi)有問(wèn)題,但是如果他們想要同時(shí)訪問(wèn)數(shù)據(jù)庫(kù)的同一條記錄,就有可能產(chǎn)生并發(fā)問(wèn)題 ( Concurrency Problem),也叫競(jìng)爭(zhēng)條件 (Race Conditions)。
比如下面的例子:
用戶1和用戶2都想增加數(shù)據(jù)庫(kù)中 counter 的值。在修改之前是42。用戶1讀到值為42,在用戶1改變 counter 的值之前,用戶2也讀到了當(dāng)前值為42,然后用戶1和用戶2分別在原始值上加1,得到43,然后寫入了數(shù)據(jù)庫(kù)。最終數(shù)據(jù)庫(kù)中 counter 的值變成了43,但實(shí)際正確的值應(yīng)該是44。
隔離性要解決的就是這個(gè)并發(fā)問(wèn)題。隔離性意思是并發(fā)執(zhí)行的事務(wù)之前彼此相互隔離,不應(yīng)該相互影響。
隔離性是 ACID 中最復(fù)雜的,也存在著很多概念上的爭(zhēng)議。大家可能認(rèn)為隔離性就是可串行化 (Serializability)。意思是數(shù)據(jù)庫(kù)需要保證這些事務(wù)在并發(fā)情況下運(yùn)行最后的結(jié)果需要和串行運(yùn)行這些事務(wù)的結(jié)果一致。實(shí)際上這確實(shí)是一種隔離性的實(shí)現(xiàn)方案,也是最高的隔離級(jí)別。但是這種實(shí)現(xiàn)卻很少會(huì)被用到,因?yàn)檫@會(huì)導(dǎo)致性能嚴(yán)重受到影響。有些數(shù)據(jù)庫(kù)甚至根本沒(méi)有實(shí)現(xiàn)這種方案,比如Oracle。Oracle 中有一種隔離級(jí)別是“可串行的” (Serializable),但是他實(shí)現(xiàn)的是一種較弱的隔離級(jí)別--快照隔離 (Snapshot Isolation),有種掛羊頭賣狗肉的感覺(jué)... 各種隔離級(jí)別的區(qū)分我們會(huì)在下一篇文章中介紹。
2.4. 持久性
持久性承諾一旦一個(gè)事務(wù)被成功完成,所有的數(shù)據(jù)改動(dòng)將不會(huì)回滾。即使硬盤壞了也能保證數(shù)據(jù)可以被持久存儲(chǔ)。
實(shí)際上沒(méi)有任何技術(shù)可以保證數(shù)據(jù)絕對(duì)持久保存。大家都只是用一些減少數(shù)據(jù)丟失的技術(shù),比如寫到硬盤、備份等等。
總結(jié)
這篇文章介紹了什么是事務(wù)以及數(shù)據(jù)庫(kù)安全保證中的 ACID 的含義。下一篇文章將繼續(xù)介紹事務(wù)的更多細(xì)節(jié)。
參考文獻(xiàn)
[1] Kleppmann, Martin. Designing data-intensive applications: The big ideas behind reliable, scalable, and maintainable systems. " O'Reilly Media, Inc.", 2017.