ClickHouse 特性及底層存儲原理

ClickHouse 是一款 MPP 架構的列式存儲數據庫,但 MPP 和列式存儲並不是什麼 " 稀罕 " 的設計。擁有類似架構的其他數據庫產品也有很多,但是爲什麼偏偏只有 ClickHouse 的性能如此出衆呢?ClickHouse 發展至今的演進過程一共經歷了四個階段,每一次階段演進,相比之前都進一步取其精華去其糟粕。可以說 ClickHouse 汲取了各家技術的精髓,將每一個細節都做到了極致。接下來將介紹 ClickHouse 的一些核心特性,正是這些特性形成的合力使得 ClickHouse 如此優秀。

1. 完備的 DBMS 功能

ClickHouse 擁有完備的管理功能,所以它稱得上是一個 DBMS ( Database Management System,數據庫管理系統 ),而不僅是一個數據庫。作爲一個 DBMS,它具備了一些基本功能,如下所示。

•DDL ( 數據定義語言 ):可以動態地創建、修改或刪除數據庫、表和視圖,而無須重啓服務。

•DML ( 數據操作語言 ):可以動態查詢、插入、修改或刪除數據。

• 權限控制:可以按照用戶粒度設置數據庫或者表的操作權限,保障數據的安全性。

• 數據備份與恢復:提供了數據備份導出與導入恢復機制,滿足生產環境的要求。

• 分佈式管理:提供集羣模式,能夠自動管理多個數據庫節點。

這裏只列舉了一些最具代表性的功能,但已然足以表明爲什麼 Click House 稱得上是 DBMS 了。

2. 列式存儲與數據壓縮

列式存儲和數據壓縮,對於一款高性能數據庫來說是必不可少的特性。一個非常流行的觀點認爲,如果你想讓查詢變得更快,最簡單且有效的方法是減少數據掃描範圍和數據傳輸時的大小,而列式存儲和數據壓縮就可以幫助我們實現上述兩點。列式存儲和數據壓縮通常是伴生的,因爲一般來說列式存儲是數據壓縮的前提。

按列存儲與按行存儲相比,前者可以有效減少查詢時所需掃描的數據量,這一點可以用一個示例簡單說明。假設一張數據表 A 擁有 50 個字段 A1~A50,以及 100 行數據。

按列存儲相比按行存儲的另一個優勢是對數據壓縮的友好性。同樣可以用一個示例簡單說明壓縮的本質是什麼。假設有兩個字符串 abcdefghi 和 bcdefghi,現在對它們進行壓縮,如下所示:

壓縮前:abcdefghi_bcdefghi

壓縮後:abcdefghi_(9,8)

可以看到,壓縮的本質是按照一定步長對數據進行匹配掃描,當發現重複部分的時候就進行編碼轉換。例如上述示例中的 (9,8),表示如果從下劃線開始向前移動 9 個字節,會匹配到 8 個字節長度的重複項,即這裏的 bcdefghi。

真實的壓縮算法自然比這個示例更爲複雜,但壓縮的實質就是如此。數據中的重複項越多,則壓縮率越高;壓縮率越高,則數據體量越小;而數據體量越小,則數據在網絡中的傳輸越快,對網絡帶寬和磁盤 IO 的壓力也就越小。既然如此,那怎樣的數據最可能具備重複的特性呢?答案是屬於同一個列字段的數據,因爲它們擁有相同的數據類型和現實語義,重複項的可能性自然就更高。

ClickHouse 就是一款使用列式存儲的數據庫,數據按列進行組織,屬於同一列的數據會被保存在一起,列與列之間也會由不同的文件分別保存 ( 這裏主要指 MergeTree 表引擎 )。數據默認使用 LZ4 算法壓縮,在 Yandex.Metrica 的生產環境中,數據總體的壓縮比可以達到 8:1 ( 未壓縮前 17PB,壓縮後 2PB )。列式存儲除了降低 IO 和存儲的壓力之外,還爲向量化執行做好了鋪墊。

3. 向量化執行引擎

坊間有句玩笑,即 " 能用錢解決的問題,千萬別花時間 "。而業界也有種調侃如出一轍,即 " 能升級硬件解決的問題,千萬別優化程序 "。有時候,你千辛萬苦優化程序邏輯帶來的性能提升,還不如直接升級硬件來得簡單直接。這雖然只是一句玩笑不能當真,但硬件層面的優化確實是最直接、最高效的提升途徑之一。向量化執行就是這種方式的典型代表,這項寄存器硬件層面的特性,爲上層應用程序的性能帶來了指數級的提升。

向量化執行,可以簡單地看作一項消除程序中循環的優化。這裏用一個形象的例子比喻。小胡經營了一家果汁店,雖然店裏的鮮榨蘋果汁深受大家喜愛,但客戶總是抱怨製作果汁的速度太慢。小胡的店裏只有一臺榨汁機,每次他都會從籃子裏拿出一個蘋果,放到榨汁機內等待出汁。如果有 8 個客戶,每個客戶都點了一杯蘋果汁,那麼小胡需要重複循環 8 次上述的榨汁流程,才能榨出 8 杯蘋果汁。如果製作一杯果汁需要 5 分鐘,那麼全部製作完畢則需要 40 分鐘。爲了提升果汁的製作速度,小胡想出了一個辦法。他將榨汁機的數量從 1 臺增加到了 8 臺,這麼一來,他就可以從籃子裏一次性拿出 8 個蘋果,分別放入 8 臺榨汁機同時榨汁。此時,小胡只需要 5 分鐘就能夠製作出 8 杯蘋果汁。爲了製作 n 杯果汁,非向量化執行的方式是用 1 臺榨汁機重複循環制作 n 次,而向量化執行的方式是用 n 臺榨汁機只執行 1 次。

爲了實現向量化執行,需要利用 CPU 的 SIMD 指令。SIMD 的全稱是 Single Instruction Multiple Data,即用單條指令操作多條數據。現代計算機系統概念中,它是通過數據並行以提高性能的一種實現方式 ( 其他的還有指令級並行和線程級並行 ),它的原理是在 CPU 寄存器層面實現數據的並行操作。

在計算機系統的體系結構中,存儲系統是一種層次結構。典型服務器計算機的存儲層次結構如圖 1 所示。一個實用的經驗告訴我們,存儲媒介距離 CPU 越近,則訪問數據的速度越快。

從上圖中可以看到,從左向右,距離 CPU 越遠,則數據的訪問速度越慢。從寄存器中訪問數據的速度,是從內存訪問數據速度的 300 倍,是從磁盤中訪問數據速度的 3000 萬倍。所以利用 CPU 向量化執行的特性,對於程序的性能提升意義非凡。

ClickHouse 目前利用 SSE4.2 指令集實現向量化執行。

4. 關係模型與 SQL 查詢

相比 HBase 和 Redis 這類 NoSQL 數據庫,ClickHouse 使用關係模型描述數據並提供了傳統數據庫的概念 ( 數據庫、表、視圖和函數等 )。與此同時,ClickHouse 完全使用 SQL 作爲查詢語言 ( 支持 GROUP BY、ORDER BY、JOIN、IN 等大部分標準 SQL ),這使得它平易近人,容易理解和學習。因爲關係型數據庫和 SQL 語言,可以說是軟件領域發展至今應用最爲廣泛的技術之一,擁有極高的 " 羣衆基礎 "。也正因爲 ClickHouse 提供了標準協議的 SQL 查詢接口,使得現有的第三方分析可視化系統可以輕鬆與它集成對接。在 SQL 解析方面,ClickHouse 是大小寫敏感的,這意味着 SELECT a 和 SELECT A 所代表的語義是不同的。

關係模型相比文檔和鍵值對等其他模型,擁有更好的描述能力,也能夠更加清晰地表述實體間的關係。更重要的是,在 OLAP 領域,已有的大量數據建模工作都是基於關係模型展開的 ( 星型模型、雪花模型乃至寬表模型 )。ClickHouse 使用了關係模型,所以將構建在傳統關係型數據庫或數據倉庫之上的系統遷移到 ClickHouse 的成本會變得更低,可以直接沿用之前的經驗成果。

5. 多樣化的表引擎

也許因爲 Yandex.Metrica 的最初架構是基於 MySQL 實現的,所以在 ClickHouse 的設計中,能夠察覺到一些 MySQL 的影子,表引擎的設計就是其中之一。與 MySQL 類似,ClickHouse 也將存儲部分進行了抽象,把存儲引擎作爲一層獨立的接口。截至本書完稿時,ClickHouse 共擁有合併樹、內存、文件、接口和其他 6 大類 20 多種表引擎。其中每一種表引擎都有着各自的特點,用戶可以根據實際業務場景的要求,選擇合適的表引擎使用。

通常而言,一個通用系統意味着更廣泛的適用性,能夠適應更多的場景。但通用的另一種解釋是平庸,因爲它無法在所有場景內都做到極致。

在軟件的世界中,並不會存在一個能夠適用任何場景的通用系統,爲了突出某項特性,勢必會在別處有所取捨。其實世間萬物都遵循着這樣的道理,就像信天翁和蜂鳥,雖然都屬於鳥類,但它們各自的特點卻鑄就了完全不同的體貌特徵。信天翁擅長遠距離飛行,環繞地球一週只需要 1 至 2 個月的時間。因爲它能夠長時間處於滑行狀態,5 天才需要扇動一次翅膀,心率能夠保持在每分鐘 100 至 200 次之間。而蜂鳥能夠垂直懸停飛行,每秒可以揮動翅膀 70~100 次,飛行時的心率能夠達到每分鐘 1000 次。如果用數據庫的場景類比信天翁和蜂鳥的特點,那麼信天翁代表的可能是使用普通硬件就能實現高性能的設計思路,數據按粗粒度處理,通過批處理的方式執行;而蜂鳥代表的可能是按細粒度處理數據的設計思路,需要高性能硬件的支持。

將表引擎獨立設計的好處是顯而易見的,通過特定的表引擎支撐特定的場景,十分靈活。對於簡單的場景,可直接使用簡單的引擎降低成本,而複雜的場景也有合適的選擇。

6. 多線程與分佈式

ClickHouse 幾乎具備現代化高性能數據庫的所有典型特徵,對於可以提升性能的手段可謂是一一用盡,對於多線程和分佈式這類被廣泛使用的技術,自然更是不在話下。

如果說向量化執行是通過數據級並行的方式提升了性能,那麼多線程處理就是通過線程級並行的方式實現了性能的提升。相比基於底層硬件實現的向量化執行 SIMD,線程級並行通常由更高層次的軟件層面控制。現代計算機系統早已普及了多處理器架構,所以現今市面上的服務器都具備良好的多核心多線程處理能力。由於 SIMD 不適合用於帶有較多分支判斷的場景,ClickHouse 也大量使用了多線程技術以實現提速,以此和向量化執行形成互補。

如果一個籃子裝不下所有的雞蛋,那麼就多用幾個籃子來裝,這就是分佈式設計中分而治之的基本思想。同理,如果一臺服務器性能喫緊,那麼就利用多臺服務的資源協同處理。爲了實現這一目標,首先需要在數據層面實現數據的分佈式。因爲在分佈式領域,存在一條金科玉律—計算移動比數據移動更加划算。在各服務器之間,通過網絡傳輸數據的成本是高昂的,所以相比移動數據,更爲聰明的做法是預先將數據分佈到各臺服務器,將數據的計算查詢直接下推到數據所在的服務器。ClickHouse 在數據存取方面,既支持分區 ( 縱向擴展,利用多線程原理 ),也支持分片 ( 橫向擴展,利用分佈式原理 ),可以說是將多線程和分佈式的技術應用到了極致。

7. 多主架構

HDFS、Spark、HBase 和 Elasticsearch 這類分佈式系統,都採用了 Master-Slave 主從架構,由一個管控節點作爲 Leader 統籌全局。而 ClickHouse 則採用 Multi-Master 多主架構,集羣中的每個節點角色對等,客戶端訪問任意一個節點都能得到相同的效果。這種多主的架構有許多優勢,例如對等的角色使系統架構變得更加簡單,不用再區分主控節點、數據節點和計算節點,集羣中的所有節點功能相同。所以它天然規避了單點故障的問題,非常適合用於多數據中心、異地多活的場景。

8. 在線查詢

ClickHouse 經常會被拿來與其他的分析型數據庫作對比,比如 Vertica、SparkSQL、Hive 和 Elasticsearch 等,它與這些數據庫確實存在許多相似之處。例如,它們都可以支撐海量數據的查詢場景,都擁有分佈式架構,都支持列存、數據分片、計算下推等特性。這其實也側面說明了 ClickHouse 在設計上確實吸取了各路奇技淫巧。與其他數據庫相比,ClickHouse 也擁有明顯的優勢。例如,Vertica 這類商用軟件價格高昂;SparkSQL 與 Hive 這類系統無法保障 90% 的查詢在 1 秒內返回,在大數據量下的複雜查詢可能會需要分鐘級的響應時間;而 Elasticsearch 這類搜索引擎在處理億級數據聚合查詢時則顯得捉襟見肘。

正如 ClickHouse 的 " 廣告詞 " 所言,其他的開源系統太慢,商用的系統太貴,只有 Clickouse 在成本與性能之間做到了良好平衡,即又快又開源。ClickHouse 當之無愧地闡釋了 " 在線 " 二字的含義,即便是在複雜查詢的場景下,它也能夠做到極快響應,且無須對數據進行任何預處理加工。

9. 數據分片與分佈式查詢

數據分片是將數據進行橫向切分,這是一種在面對海量數據的場景下,解決存儲和查詢瓶頸的有效手段,是一種分治思想的體現。ClickHouse 支持分片,而分片則依賴集羣。每個集羣由 1 到多個分片組成,而每個分片則對應了 ClickHouse 的 1 個服務節點。分片的數量上限取決於節點數量 ( 1 個分片只能對應 1 個服務節點 )。

ClickHouse 並不像其他分佈式系統那樣,擁有高度自動化的分片功能。ClickHouse 提供了本地表 (Local Table) 與分佈式表 (Distributed Table) 的概念。一張本地表等同於一份數據的分片。而分佈式表本身不存儲任何數據,它是本地表的訪問代理,其作用類似分庫中間件。藉助分佈式表,能夠代理訪問多個數據分片,從而實現分佈式查詢。

這種設計類似數據庫的分庫和分表,十分靈活。例如在業務系統上線的初期,數據體量並不高,此時數據表並不需要多個分片。所以使用單個節點的本地表 ( 單個數據分片 ) 即可滿足業務需求,待到業務增長、數據量增大的時候,再通過新增數據分片的方式分流數據,並通過分佈式表實現分佈式查詢。這就好比一輛手動擋賽車,它將所有的選擇權都交到了使用者的手中。

****-   ****ClickHouse 存儲層  -

ClickHouse 從 OLAP 場景需求出發,定製開發了一套全新的高效列式存儲引擎,並且實現了數據有序存儲、主鍵索引、稀疏索引、數據 Sharding、數據 Partitioning、TTL、主備複製等豐富功能。以上功能共同爲 ClickHouse 極速的分析性能奠定了基礎。

列式存儲

與行存將每一行的數據連續存儲不同,列存將每一列的數據連續存儲。示例圖如下:

相比於行式存儲,列式存儲在分析場景下有着許多優良的特性。

1)如前所述,分析場景中往往需要讀大量行但是少數幾個列。在行存模式下,數據按行連續存儲,所有列的數據都存儲在一個 block 中,不參與計算的列在 IO 時也要全部讀出,讀取操作被嚴重放大。而列存模式下,只需要讀取參與計算的列即可,極大的減低了 IO cost,加速了查詢。

2)同一列中的數據屬於同一類型,壓縮效果顯著。列存往往有着高達十倍甚至更高的壓縮比,節省了大量的存儲空間,降低了存儲成本。

3)更高的壓縮比意味着更小的 data size,從磁盤中讀取相應數據耗時更短。

4)自由的壓縮算法選擇。不同列的數據具有不同的數據類型,適用的壓縮算法也就不盡相同。可以針對不同列類型,選擇最合適的壓縮算法。

5)高壓縮比,意味着同等大小的內存能夠存放更多數據,系統 cache 效果更好。

數據有序存儲

ClickHouse 支持在建表時,指定將數據按照某些列進行 sort by。

排序後,保證了相同 sort key 的數據在磁盤上連續存儲,且有序擺放。在進行等值、範圍查詢時,where 條件命中的數據都緊密存儲在一個或若干個連續的 Block 中,而不是分散的存儲在任意多個 Block, 大幅減少需要 IO 的 block 數量。另外,連續 IO 也能夠充分利用操作系統 page cache 的預取能力,減少 page fault。

主鍵索引

ClickHouse 支持主鍵索引,它將每列數據按照 index granularity(默認 8192 行)進行劃分,每個 index granularity 的開頭第一行被稱爲一個 mark 行。主鍵索引存儲該 mark 行對應的 primary key 的值。

對於 where 條件中含有 primary key 的查詢,通過對主鍵索引進行二分查找,能夠直接定位到對應的 index granularity,避免了全表掃描從而加速查詢。

但是值得注意的是:ClickHouse 的主鍵索引與 MySQL 等數據庫不同,它並不用於去重,即便 primary key 相同的行,也可以同時存在於數據庫中。要想實現去重效果,需要結合具體的表引擎 ReplacingMergeTree、CollapsingMergeTree、VersionedCollapsingMergeTree 實現,我們會在未來的文章系列中再進行詳細解讀。

數據插入、更新、刪除

Clickhouse 是個分析型數據庫。這種場景下,數據一般是不變的,因此 Clickhouse 對 update、delete 的支持是比較弱的,實際上並不支持標準的 update、delete 操作。

Clickhouse 通過 alter 方式實現更新、刪除,它把 update、delete 操作叫做 mutation(突變)。

標準 SQL 的更新、刪除操作是同步的,即客戶端要等服務端反回執行結果(通常是 int 值);而 Clickhouse 的 update、delete 是通過異步方式實現的,當執行 update 語句時,服務端立即反回,但是實際上此時數據還沒變,而是排隊等着。

Mutation 具體過程

首先,使用 where 條件找到需要修改的分區;然後,重建每個分區,用新的分區替換舊的,分區一旦被替換,就不可回退;對於每個分區,可以認爲是原子性的;但對於整個 mutation,如果涉及多個分區,則不是原子性的。

• 更新功能不支持更新有關主鍵或分區鍵的列。

• 更新操作沒有原子性,即在更新過程中 select 結果很可能是一部分變了,一部分沒變,從上邊的具體過程就可以知道。

• 更新是按提交的順序執行的。

• 更新一旦提交,不能撤銷,即使重啓 Clickhouse 服務,也會繼續按照 system.mutations 的順序繼續執行。

• 已完成更新的條目不會立即刪除,保留條目的數量由 finished_mutations_to_keep 存儲引擎參數確定。超過數據量時舊的條目會被刪除。

• 更新可能會卡住,比如 update intvalue='abc’這種類型錯誤的更新語句執行不過去,那麼會一直卡在這裏,此時,可以使用 KILL MUTATION 來取消。

使用建議

按照官方的說明,update/delete 的使用場景是一次更新大量數據,也就是 where 條件篩選的結果應該是一大片數據。

舉例:alter table test update status=1 where status=0 and day='2020-04-01',一次更新一天的數據。

那麼,能否一次只更新一條數據呢?例如:alter table test update pv=110 where id=100 當然也可以,但頻繁的這種操作,可能會對服務造成壓力。這很容易理解,如上文提到,更新的單位是分區,如果只更新一條數據,那麼需要重建一個分區;如果更新 100 條數據,而這 100 條可能落在 3 個分區上,則需重建 3 個分區;相對來說一次更新一批數據的整體效率遠高於一次更新一行。對於頻繁單條更新的這種場景,建議使用 ReplacingMergeTree 引擎來變相解決。具體如何使用,以後有時間再整理。

Hbase 隨機讀寫,但是 Hbase 的 update 操作不是真的 update,它的實際操作是 insert 一條新的數據,打上不同的 timestamp,而老的數據會在有效期之後自動刪除。而 Clickhouse 乾脆就不支持 update 和 delete。

****-   ****ClickHouse 核心涉及模塊 -

1. Column 與 Field

Column 和 Field 是 ClickHouse 數據最基礎的映射單元。作爲一款百分之百的列式存儲數據庫,ClickHouse 按列存儲數據,內存中的一列數據由一個 Column 對象表示。Column 對象分爲接口和實現兩個部分,在 IColumn 接口對象中,定義了對數據進行各種關係運算的方法,例如插入數據的 insertRangeFrom 和 insertFrom 方法、用於分頁的 cut,以及用於過濾的 filter 方法等。而這些方法的具體實現對象則根據數據類型的不同,由相應的對象實現,例如 ColumnString、ColumnArray 和 ColumnTuple 等。在大多數場合,ClickHouse 都會以整列的方式操作數據,但凡事也有例外。如果需要操作單個具體的數值 ( 也就是單列中的一行數據 ),則需要使用 Field 對象,Field 對象代表一個單值。與 Column 對象的泛化設計思路不同,Field 對象使用了聚合的設計模式。在 Field 對象內部聚合了 Null、UInt64、String 和 Array 等 13 種數據類型及相應的處理邏輯。

2. DataType

數據的序列化和反序列化工作由 DataType 負責。IDataType 接口定義了許多正反序列化的方法,它們成對出現,例如 serializeBinary 和 deserializeBinary、serializeTextJSON 和 deserializeTextJSON 等,涵蓋了常用的二進制、文本、JSON、XML、CSV 和 Protobuf 等多種格式類型。IDataType 也使用了泛化的設計模式,具體方法的實現邏輯由對應數據類型的實例承載,例如 DataTypeString、DataTypeArray 及 DataTypeTuple 等。

DataType 雖然負責序列化相關工作,但它並不直接負責數據的讀取,而是轉由從 Column 或 Field 對象獲取。在 DataType 的實現類中,聚合了相應數據類型的 Column 對象和 Field 對象。例如,DataTypeString 會引用字符串類型的 ColumnString,而 DataTypeArray 則會引用數組類型的 ColumnArray,以此類推。

3. Block 與 Block 流

ClickHouse 內部的數據操作是面向 Block 對象進行的,並且採用了流的形式。雖然 Column 和 Filed 組成了數據的基本映射單元,但對應到實際操作,它們還缺少了一些必要的信息,比如數據的類型及列的名稱。於是 ClickHouse 設計了 Block 對象,Block 對象可以看作數據表的子集。Block 對象的本質是由數據對象、數據類型和列名稱組成的三元組,即 Column、DataType 及列名稱字符串。Column 提供了數據的讀取能力,而 DataType 知道如何正反序列化,所以 Block 在這些對象的基礎之上實現了進一步的抽象和封裝,從而簡化了整個使用的過程,僅通過 Block 對象就能完成一系列的數據操作。在具體的實現過程中,Block 並沒有直接聚合 Column 和 DataType 對象,而是通過 ColumnWithTypeAndName 對象進行間接引用。

有了 Block 對象這一層封裝之後,對 Block 流的設計就是水到渠成的事情了。流操作有兩組頂層接口:IBlockInputStream 負責數據的讀取和關係運算,IBlockOutputStream 負責將數據輸出到下一環節。Block 流也使用了泛化的設計模式,對數據的各種操作最終都會轉換成其中一種流的實現。IBlockInputStream 接口定義了讀取數據的若干個 read 虛方法,而具體的實現邏輯則交由它的實現類來填充。

IBlockInputStream 接口總共有 60 多個實現類,它們涵蓋了 ClickHouse 數據攝取的方方面面。這些實現類大致可以分爲三類:第一類用於處理數據定義的 DDL 操作,例如 DDLQueryStatusInputStream 等;第二類用於處理關係運算的相關操作,例如 LimitBlockInput-Stream、JoinBlockInputStream 及 AggregatingBlockInputStream 等;第三類則是與表引擎呼應,每一種表引擎都擁有與之對應的 BlockInputStream 實現,例如 MergeTreeBaseSelect-BlockInputStream ( MergeTree 表引擎 )、TinyLogBlockInputStream ( TinyLog 表引擎 ) 及 KafkaBlockInputStream ( Kafka 表引擎 ) 等。

IBlockOutputStream 的設計與 IBlockInputStream 如出一轍。IBlockOutputStream 接口同樣也定義了若干寫入數據的 write 虛方法。它的實現類比 IBlockInputStream 要少許多,一共只有 20 多種。這些實現類基本用於表引擎的相關處理,負責將數據寫入下一環節或者最終目的地,例如 MergeTreeBlockOutputStream 、TinyLogBlockOutputStream 及 StorageFileBlock-OutputStream 等。

4. Table

在數據表的底層設計中並沒有所謂的 Table 對象,它直接使用 IStorage 接口指代數據表。表引擎是 ClickHouse 的一個顯著特性,不同的表引擎由不同的子類實現,例如 IStorageSystemOneBlock ( 系統表 )、StorageMergeTree ( 合併樹表引擎 ) 和 StorageTinyLog ( 日誌表引擎 ) 等。IStorage 接口定義了 DDL ( 如 ALTER、RENAME、OPTIMIZE 和 DROP 等 ) 、read 和 write 方法,它們分別負責數據的定義、查詢與寫入。在數據查詢時,IStorage 負責根據 AST 查詢語句的指示要求,返回指定列的原始數據。後續對數據的進一步加工、計算和過濾,則會統一交由 Interpreter 解釋器對象處理。對 Table 發起的一次操作通常都會經歷這樣的過程,接收 AST 查詢語句,根據 AST 返回指定列的數據,之後再將數據交由 Interpreter 做進一步處理。

作者:四季寫愛

來源:https://www.cnblogs.com/ya-qiang/p/13680283.html

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/8TSufVkKXYMusS79qWf5Hw