從數(shù)據(jù)庫的角度看待區(qū)塊鏈的存儲機制會簡單直觀很多。在一個標準的關系型數(shù)據(jù)庫中,存儲一般分為日志存儲、用戶數(shù)據(jù)存儲、以及索引存儲三大類(有些數(shù)據(jù)庫可能還包含大對象存儲等)。
而區(qū)塊鏈項目中基本所有的“賬本”存儲其本質(zhì)就是交易日志存儲。用戶數(shù)據(jù)存儲則根據(jù)項目不同而有選擇性地采用。譬如說對于UTXO結構的區(qū)塊鏈項目來說,其每個賬號對應的余額直接保存在內(nèi)存哈希表中(或類似LevelDB等嵌入式KV數(shù)據(jù)庫中),因此不需要一個獨立的外接用戶數(shù)據(jù)存儲模塊。而類似Hyperledger等通用區(qū)塊鏈框架則一般包含類似State Store等存儲最終結果數(shù)據(jù)的模塊。索引存儲則在當前大部分區(qū)塊鏈項目中均不存在。
1.賬本格式
區(qū)塊鏈的鏈式結構筆者在這里不再贅述,其每個塊包含上一個塊的哈希值,而內(nèi)容則使用默克爾樹進行校驗以實現(xiàn)快速驗證,每條記錄則使用數(shù)字簽名的方式保證其一定來自擁有私鑰的用戶。
圖1:區(qū)塊鏈數(shù)據(jù)結構
對比傳統(tǒng)數(shù)據(jù)庫的日志結構,區(qū)塊鏈結構并不復雜。一般來說,所有傳統(tǒng)數(shù)據(jù)庫日志結構都比較相似,每個日志文件包含日志頭與多個日志數(shù)據(jù)頁,其中日志頭代表其起始交易號(Oracle中叫做SCN,DB2中叫做LSN),也就是該日志文件中***條日志記錄在整個日志空間中的起始字節(jié)數(shù),以及日志文件大小,日志頁大小等元數(shù)據(jù)信息。緊接著真實的日志記錄則以二進制碼流的方式依次存放在文件中,每一條日志記錄頭包含該條記錄的交易號、事務號、同一事務中上一條記錄的交易號(反向指針),以及變更前與變更后的數(shù)據(jù)(滿足回滾要求)。
圖2:數(shù)據(jù)庫日志結構
因此,從結構上來看,區(qū)塊鏈賬本與數(shù)據(jù)庫日志本質(zhì)上沒有任何區(qū)別,僅僅在數(shù)據(jù)結構上為了滿足一些特定要求做了部分優(yōu)化。
2. 存儲內(nèi)容
通用型數(shù)據(jù)庫與當前大部分區(qū)塊鏈賬本項目(例如比特幣、以太坊等,而Hyperledger這類區(qū)塊鏈平臺則不包含在內(nèi))從日志的角度看,***的區(qū)別在于區(qū)塊鏈賬本項目對于日志格式進行了高度定制化與業(yè)務綁定。
一般來說,傳統(tǒng)的數(shù)據(jù)庫日志包含的是數(shù)據(jù)頁的變更信息,我們叫做“寫前鏡像”和“寫后鏡像”,代表新的數(shù)據(jù)寫入前這條記錄長什么樣子,同時新數(shù)據(jù)寫入后這個記錄長什么樣子。通過這種方式,可以很輕易地在磁盤中的某個數(shù)據(jù)頁的指定偏移上進行數(shù)據(jù)前滾與回滾操作。
譬如說一條數(shù)據(jù)庫日志并不會記錄一個INSERT操作的具體命令,而是以“X數(shù)據(jù)頁的第Y個槽位,其所對應的偏移地址數(shù)據(jù)由ABC變化為DEF,總長度Z”的方式體現(xiàn)出來。因此,數(shù)據(jù)庫的日志幾乎可以記錄任何信息,這也是為什么通用數(shù)據(jù)庫可以被用來實現(xiàn)任何業(yè)務邏輯的原因。
而大部分的賬本項目則高度定制化其日志結構,例如以太坊的每一條交易信息都會包含輸入金額、輸出金額、燃料等信息,然后每一個節(jié)點在進行驗證時必須判斷其符合某種規(guī)則,否則不予通過。這種機制可以看做是一個完全高度定制化的數(shù)據(jù)庫業(yè)務,每一條日志記錄不僅僅記錄數(shù)據(jù)內(nèi)容的變更,而是與業(yè)務邏輯緊密耦合,記錄每個賬戶的余額變化。
因此,從可擴展性來看,盡管以太坊等項目支持“智能合約”,但是其核心本質(zhì)還是一個高度定制化的賬本系統(tǒng),其業(yè)務邏輯與交易結算進行了非常緊密的綁定。
3. 設計思路
從設計哲學上看,當前的區(qū)塊鏈與數(shù)據(jù)庫在對通用業(yè)務的支持上采用了兩種不同的策略。對于傳統(tǒng)數(shù)據(jù)庫來說,其秉承的設計理念是“業(yè)務與數(shù)據(jù)分離”的思路,也就是說數(shù)據(jù)庫僅負責數(shù)據(jù)的存放,通過提供一種靈活的查詢語言能夠讓應用程序直接訪問數(shù)據(jù)庫進行增刪改查,但是基本所有的業(yè)務邏輯由應用程序自行定義。
但是,對于區(qū)塊鏈來說則是存儲與業(yè)務邏輯緊耦合的思路。在區(qū)塊鏈,尤其是公鏈的設計哲學中,因為每個存儲節(jié)點和應用都是不可信的,因此大部分業(yè)務邏輯需要在協(xié)議層進行高度定制。一個區(qū)塊鏈節(jié)點即需要對協(xié)議層進行解析和封裝,同時也需要負責對數(shù)據(jù)本地化落盤和存儲。
因而,盡管站在高層面可以將區(qū)塊鏈看做是多活數(shù)據(jù)庫,但是如果從具體實現(xiàn)層面來看,每個區(qū)塊鏈節(jié)點又不能簡單地看做是一個傳統(tǒng)數(shù)據(jù)庫的多活替代品,而是一套包含協(xié)議解析封裝和一部分業(yè)務邏輯的應用軟件。
4. 用戶數(shù)據(jù)存儲
如果未來區(qū)塊鏈的目標是作為通用平臺,用以存儲多種類型的數(shù)據(jù),則其日志格式與存儲必須回歸數(shù)據(jù)庫的通用性本源。當前的賬本模式可以作為該體系中的一個特別模塊存在用以進行賬戶間結算,但是無法將其擴展為通用業(yè)務平臺。
既然要成為通用數(shù)據(jù)存儲平臺,那么UTXO模型存在一定局限性。在一個典型的銀行業(yè)務中,零售業(yè)務可能會包含千萬甚至億級別的賬戶,不同賬戶可能使用不同的利息計算規(guī)則,也可能存在凍結等特殊狀態(tài)。而交易流水信息每天可能達到千萬筆,如果將其業(yè)務擴展到非金融行業(yè),流水信息每天幾億也是可能的。因此,從一個通用賬戶+流水的業(yè)務模型中,一般企業(yè)會建立一個賬戶表與一個流水表,以不同的策略進行管理。
賬戶表俗稱余額類數(shù)據(jù),在典型的數(shù)據(jù)治理體系中需要做到定期快照備份(例如月初數(shù)和月末數(shù));而流水表則成為流水類數(shù)據(jù),一般來說以原始交易格式直接存儲和備份。通過對余額類數(shù)據(jù)快照備份的恢復,對指定賬號重做某個時間范圍內(nèi)的全部交易流水,可以得到該賬號任意時間點的余額信息。
而UTXO的本質(zhì)在于日志存放的信息不是記錄的最終結果,而是變化行為。在傳統(tǒng)數(shù)據(jù)庫中,每條事務記錄的是數(shù)據(jù)的寫前與寫后內(nèi)容。例如將一條記錄從5更改為8,其數(shù)據(jù)庫日志記錄原始數(shù)據(jù)為5且新數(shù)據(jù)為8,而不是記錄“+3”的操作。但是UTXO記錄的是變更信息,其主要的目的是解決雙花問題(例如對于一個有100塊錢的賬號,一個人在中國轉(zhuǎn)走10塊錢,另一個人在美國同時轉(zhuǎn)走10塊錢,如果記錄的是最終結果,那么中國的服務器會認為這個人有90塊,美國的服務器在沒有全局鎖的情況下也會認為這個人有90塊,最終寫到區(qū)塊中就變成90塊余額,而非80)。
UTXO的機制可以有效地在無鎖的情況下避免雙花問題,但是其劣勢則在于不存儲余額表,所有的信息均通過重做流水數(shù)據(jù),從零開始生成。對于一個存在了十年以上,包含幾百億筆交易的系統(tǒng)來說,這樣的做法就好比每次重啟都要從都重做幾百筆交易并存入內(nèi)存中(或KV數(shù)據(jù)庫里),是一種非常原始且不經(jīng)濟的方式。
另一方面,區(qū)塊鏈日志的結構看來,由于多活系統(tǒng)中全局鎖很難實現(xiàn),因此需要通過交易日志結構的調(diào)整來滿足傳統(tǒng)數(shù)據(jù)庫中事務的功能。傳統(tǒng)數(shù)據(jù)庫中當涉及到兩賬戶之間轉(zhuǎn)賬操作時需要開啟一個事務。在事務日志中一個賬戶增加一個賬戶減少的業(yè)務邏輯,需要體現(xiàn)為包含三條記錄的鏈表(***的提交操作也是一個記錄)。在數(shù)據(jù)庫崩潰或發(fā)生異常后,只要通過重做所有的任務,并***對全部沒有提交記錄的事務進行反向操作,即可得到原子性(Atomic)與持久性(Durability)。
而在區(qū)塊鏈體系中由于不存在事務的概念,同時操作日志與結算業(yè)務進行了緊密耦合,因此每條交易記錄都會包含一個輸入賬號以及若干個輸出賬號,也就是說只要一條事務記錄被成功發(fā)送給一個節(jié)點,則可以保證在該記錄內(nèi)部的全部輸入輸出賬戶統(tǒng)一進行了變更??梢哉f,區(qū)塊鏈通過定制化交易日志簡化了事務操作的復雜性,但是帶來的影響便在于業(yè)務與代碼的緊密耦合不可分割。
但是無論如何,首先UTXO并不是通用數(shù)據(jù)結構,而是為交易業(yè)務高度定制化的數(shù)據(jù)結構,如果想要運行圖靈完備的智能合約(或者說存儲過程),使用UTXO會有很多局限性。第二,對長期運行的大型系統(tǒng)(相比起大中型銀行核心交易系統(tǒng)所產(chǎn)生的交易流水,比特幣從誕生到現(xiàn)在的交易量少得可以忽略不計),UTXO每次初始化需要全部的歷史交易日志。這種模式完全不可能適用于大型交易系統(tǒng)。
因此,可以存在兩種做法解決該問題。***種方式使用傳統(tǒng)賬戶表與流水表的機制,將UTXO以流水的方式體現(xiàn)出來,同時定期保存賬戶快照,以避免每次重構數(shù)據(jù)庫都需要重做全部交易(這種機制需要考慮到賬戶與流水表在多活系統(tǒng)中,沒有全局鎖的情況下如何實現(xiàn)一致性的問題)。而對于非結算類交易,通用型區(qū)塊鏈項目則可能采用日志結合用戶數(shù)據(jù)存儲的模式,才能夠普適性地滿足通用業(yè)務需求(這種機制需要依靠比nonce更好的排序機制避免雙花)。
5. 索引存儲
當前基本沒有任何區(qū)塊鏈項目支持用戶數(shù)據(jù)的自定義索引。這種機制在未來的通用型區(qū)塊鏈項目一定會被彌補。從本質(zhì)上看當前的區(qū)塊鏈項目結構沒有任何理由無法在其上構建通用索引能力(包括B樹索引、位圖索引、全文檢索等)。
小結
區(qū)塊鏈的存儲體系現(xiàn)在還處于數(shù)據(jù)庫上世紀80年代的階段,其當前***的問題在于日志結構與業(yè)務邏輯的緊密耦合(讀者可以理解為應用程序為每種業(yè)務邏輯都要從頭實現(xiàn)一遍Oracle)。而這樣做的本質(zhì)原因在于多活數(shù)據(jù)庫中事務的原子性與鎖極難保障,因此當涉及到多個賬戶的轉(zhuǎn)賬原子操作時,當前大部分賬本類區(qū)塊鏈項目均不得不定制日志結構,將每一筆交易的全部信息放在一條記錄中。
從數(shù)據(jù)庫的角度看,在區(qū)塊鏈項目中實現(xiàn)跨記錄的原子操作(包括全局鎖)極為復雜,而這也正式區(qū)塊鏈技術向通用型數(shù)據(jù)存儲進化的關鍵所在。