你知道你的PG數(shù)據(jù)安全準(zhǔn)確嗎
去年我寫過一篇文章《PG數(shù)據(jù)庫離企業(yè)級數(shù)據(jù)庫還有多遠(yuǎn)》,實(shí)際上對PG了解得越深入,這個(gè)問題就越值得我們?nèi)ニ伎?。前幾天一個(gè)做數(shù)據(jù)庫高可用架構(gòu)的朋友在我的公眾號上留言,說在PG數(shù)據(jù)庫中,如果刪除了某一個(gè)數(shù)據(jù)文件,PG數(shù)據(jù)庫居然不報(bào)錯,還能查出數(shù)據(jù)來,不過查出來的數(shù)據(jù)是錯的。這一點(diǎn)我以前倒是沒有注意到,數(shù)據(jù)庫丟失數(shù)據(jù)文件不報(bào)錯是正常現(xiàn)象,不過查詢數(shù)據(jù)的時(shí)候,如果掃描到了這部分內(nèi)容,按理說應(yīng)該是會報(bào)錯的,比如Oracle就是如此。昨天下班前我正好有點(diǎn)時(shí)間,就做了個(gè)小實(shí)驗(yàn)。實(shí)驗(yàn)內(nèi)容有點(diǎn)長,我先講一些結(jié)論性的東西,有興趣了解細(xì)節(jié)的朋友看完結(jié)論性的分析后再去看實(shí)驗(yàn)的詳情吧。
數(shù)據(jù)文件的完整性檢查是一個(gè)開銷十分巨大的操作,因此幾乎沒有數(shù)據(jù)庫會隨時(shí)對數(shù)據(jù)文件的完整性做檢查。連Oracle這種段頁式結(jié)構(gòu),以表空間為組織模式的數(shù)據(jù)庫都不會隨時(shí)去檢查數(shù)據(jù)文件的完整性和可用性。只有在訪問某個(gè)數(shù)據(jù)文件的時(shí)候才會通過文件頭去做一些校驗(yàn)。不過對于數(shù)據(jù)文件中的數(shù)據(jù)的一致性仍然不會去做檢查。這是一種更大開銷的操作。只有訪問到相關(guān)數(shù)據(jù)的時(shí)候才會去做一致性和完整性的檢查(并不是所有的訪問操作都會做)。不過不管如何,RDBMS系統(tǒng)要盡可能保證查詢出來的數(shù)據(jù)的邏輯一致性,確保數(shù)據(jù)一定是正確的。
對于Oracle這樣的數(shù)據(jù)庫,文件的屬性被記錄在control file中,新增一個(gè)文件或者文件的大小發(fā)生變化的時(shí)候,會自動更新數(shù)據(jù)。對于PG這樣的每張表都會有多個(gè)文件來存儲數(shù)據(jù)的數(shù)據(jù)庫來說,登記每個(gè)使用過的文件是一種十分高成本的操作,一個(gè)上TB的表可能就會擁有上千個(gè)數(shù)據(jù)文件。這是一種一致性對于性能的妥協(xié),這種妥協(xié)為PG數(shù)據(jù)庫的數(shù)據(jù)一致性帶來了巨大的隱患。昨天我的實(shí)驗(yàn)的結(jié)論是:“當(dāng)PG數(shù)據(jù)文件出現(xiàn)丟失的時(shí)候,PG數(shù)據(jù)庫不一定會因?yàn)槲募G失而報(bào)錯,而是會直接返回錯誤的數(shù)據(jù)”。這是一種十分恐怖的特性,對于關(guān)鍵性的企業(yè)級應(yīng)用來說,錯誤的數(shù)據(jù)比丟失數(shù)據(jù)還要可怕。
下面請大家看我的實(shí)驗(yàn)過程,我使用的PG版本是12.6,如果PG數(shù)據(jù)庫在新版本中已經(jīng)修復(fù)了我今天實(shí)驗(yàn)中的問題,也請朋友留言告知。先創(chuàng)建一張表,寫入部分?jǐn)?shù)據(jù)。從pg_class里可以看到relfilenode是16399。
圖片
圖片
可以看出目前數(shù)據(jù)文件存儲在3個(gè)文件里。同時(shí)有一個(gè)fsm文件記錄了空閑空間的情況。我們做個(gè)簡單的count查詢。
圖片
沒錯,我剛才寫入了1000萬條數(shù)據(jù)。然后我們開始作妖,刪除16399.1文件看看會出現(xiàn)什么情況。
圖片
在另外一個(gè)窗口用rm命令刪除了文件后,我們查一下這張表的數(shù)據(jù):
圖片
這里是報(bào)錯了,確實(shí)發(fā)現(xiàn)了剛才被刪除的文件丟失了。在多次實(shí)驗(yàn)中,我發(fā)現(xiàn)有時(shí)候不會報(bào)錯,可以直接成功。我們先不管不報(bào)錯的場景,后面我會補(bǔ)充這方面的數(shù)據(jù),現(xiàn)在我們重啟一下數(shù)據(jù)庫,再來看看。
數(shù)據(jù)庫重啟后,居然查詢成功了,只不過數(shù)據(jù)似乎不太對,少了一些數(shù)據(jù),而且少的還不是16399.1里的所有數(shù)據(jù),似乎重啟數(shù)據(jù)庫的時(shí)候做了RECOVER。
圖片
回到目錄中再去查看一下。十分奇怪,剛才被刪除的文件又回來了。
圖片
從數(shù)據(jù)庫的日志中我們可以看到,RDBMS是做了RECOVER,自動恢復(fù)了被刪除的文件。不過這個(gè)恢復(fù)并不完整,但是系統(tǒng)也沒有報(bào)錯,這種機(jī)制會給我們帶來錯覺,導(dǎo)致業(yè)務(wù)數(shù)據(jù)的錯亂,是十分可怕的。于是我再做一次刪除,然后重啟數(shù)據(jù)庫試試。十分奇怪的是,這回?cái)?shù)據(jù)庫重啟沒有像上回那樣RECOVER了丟失的數(shù)據(jù)文件,這種行為的不確定性也說明了PG數(shù)據(jù)庫在數(shù)據(jù)一致性檢查方面存在一定的缺陷,不能保持某些恢復(fù)行為的一致性,對于企業(yè)級數(shù)據(jù)庫來說,這也是十分致命的。
圖片
圖片
從文件系統(tǒng)上看,這回丟失的文件也沒有恢復(fù)。
圖片
我再查詢數(shù)據(jù),發(fā)現(xiàn)丟失了一半的數(shù)據(jù),只有一半數(shù)據(jù)了。接下來再試試CTAS,完整拷貝這張表的全部數(shù)據(jù)。這個(gè)操作居然成功完成,沒有任何報(bào)錯,這說明RDBMS認(rèn)為當(dāng)前的數(shù)據(jù)是完整的,而實(shí)際上數(shù)據(jù)已經(jīng)產(chǎn)生了嚴(yán)重的丟失。接下來測試下寫入數(shù)據(jù),這個(gè)測試也成功了。
圖片
這是一個(gè)十分恐怖的實(shí)驗(yàn),在我的理解里,F(xiàn)SM里起碼會記錄空塊的情況,丟失文件的問題應(yīng)該能從查找FSM文件的時(shí)候被發(fā)現(xiàn)。不過這沒有發(fā)生,不過也很容易理解,因?yàn)镮NSERT數(shù)據(jù)的時(shí)候,只需要查空塊就可以了,不一定會發(fā)現(xiàn)問題。目前我還沒有從PG的源碼上去分析這個(gè)問題,因此還不是很清晰這方面的機(jī)理。不過從目前的實(shí)驗(yàn)上看,PG確實(shí)存在誤刪文件后不會被發(fā)現(xiàn),導(dǎo)致數(shù)據(jù)出現(xiàn)錯誤的問題。這種缺陷是十分恐怖的,很多時(shí)候不怕丟數(shù)據(jù),而是怕丟了數(shù)據(jù)你不知道。因?yàn)閷τ诤诵臉I(yè)務(wù)系統(tǒng)來說,數(shù)據(jù)準(zhǔn)確性是最為關(guān)鍵的。
這回我們換一個(gè)玩法,首先我們創(chuàng)建好表數(shù)據(jù),然后我們關(guān)閉數(shù)據(jù)庫,再刪除某個(gè)文件。重啟數(shù)據(jù)庫。
圖片
上圖中黃線后面的操作是我重啟數(shù)據(jù)庫后做的,發(fā)現(xiàn)被刪除的文件沒有被恢復(fù)。再看看查詢結(jié)果。
圖片
如預(yù)期的那樣,沒有報(bào)錯,但是結(jié)果是錯誤的,少了400多萬條數(shù)據(jù)。剛才刪除文件前我備份了該文件,把該文件直接拷貝回來看看。
圖片
仍然沒有報(bào)錯,正確的結(jié)果回來了,是不是很神奇?。?!。從上面的實(shí)驗(yàn)可以看到,如果在PG數(shù)據(jù)庫中丟失某個(gè)數(shù)據(jù)文件,那么數(shù)據(jù)庫的行為可能是不確定的,不過大概率會給你返回錯誤的數(shù)據(jù)。這種特性會對于關(guān)鍵的企業(yè)級應(yīng)用帶來困難。因此我們必須在盡可能不影響數(shù)據(jù)庫性能的前提下彌補(bǔ)這個(gè)缺陷。至于如何彌補(bǔ),可能需要對源代碼做一些解讀后才能想辦法。今天的實(shí)驗(yàn)先到這里吧,源代碼的解讀隨后有時(shí)間再做。有興趣的朋友也可以去閱讀分析一下。