ClickHouse原理 | ClickHouse特性及底層儲存原理

語言: CN / TW / HK

ClickHouse的特性

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

01  完備的DBMS功能

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

•DDL (  資料定義語言  ) :可以動態地建立、修改或刪除資料庫、表和檢視,而無須重啟服務。

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

•許可權控制:可以按照使用者粒度設定資料庫或者表的操作許可權,保障資料的安全性。

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

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

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

02  列式儲存與資料壓縮

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

按列儲存與按行儲存相比,前者可以有效減少查詢時所需掃描的資料量,這一點可以用一個示例簡單說明。假設一張資料表 A 擁有 50 個欄位 A1A50 ,以及 100 行資料。

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

壓縮前: abcdefghi_bcdefghi

壓縮後: abcdefghi_(9,8)

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

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

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

03  向量化執行引擎

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

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

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

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

從上圖中可以看到,從左向右,距離 CPU 越遠,則資料的訪問速度越慢。從暫存器中訪問資料的速度,是從記憶體訪問資料速度的 300 倍,是從磁碟中訪問資料速度的 3000 萬倍。所以利用 CPU 向量化執行的特性,對於程式的效能提升意義非凡。

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

04  關係模型與SQL查詢

相比 HBaseRedis 這類 NoSQL 資料庫, ClickHouse 使用關係模型描述資料並提供了傳統資料庫的概念  資料庫、表、檢視和函式等  ) 。與此同時, ClickHouse 完全使用 SQL 作為查詢語言  支援 GROUP BYORDER BYJOININ 等大部分標準 SQL ) ,這使得它平易近人,容易理解和學習。因為關係型資料庫和 SQL 語言,可以說是軟體領域發展至今應用最為廣泛的技術之一,擁有極高的 " 群眾基礎 " 。也正因為 ClickHouse 提供了標準協議的 SQL 查詢介面,使得現有的第三方分析視覺化系統可以輕鬆與它整合對接。在 SQL 解析方面, ClickHouse 是大小寫敏感的,這意味著 SELECT a  和  SELECT A 所代表的語義是不同的。

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

05  多樣化的表引擎

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

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

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

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

06  多執行緒與分散式

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

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

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

07  多主架構

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

08  線上查詢

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

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

09  Leetcode超級會員資料分片與分散式查詢

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

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

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

雲原生概念的誕生ClickHouse儲存層

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

列式儲存

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

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

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

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

3 )更高的壓縮比意味著更小的 data size ,從磁碟中讀取相應資料耗時更短。

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

5 )高壓縮比,意味著同等大小的記憶體能夠存放更多資料,系統 cache 效果更好。

資料有序儲存

ClickHouse 支援在建表時,指定將資料按照某些列進行 sort by

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

主鍵索引

ClickHouse 支援主鍵索引,它將每列資料按照 index granularity (預設 8192 行)進行劃分,每個 index granularity 的開頭第一行被稱為一個 mark 行。主鍵索引儲存該 mark 行對應的 primary key 的值。

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

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

資料插入、更新、刪除

Clickhouse 是個分析型資料庫。這種場景下,資料一般是不變的,因此 Clickhouseupdatedelete 的支援是比較弱的,實際上並不支援標準的 updatedelete 操作。

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

標準 SQL 的更新、刪除操作是同步的,即客戶端要等服務端反回執行結果(通常是 int 值);而 Clickhouseupdatedelete 是通過非同步方式實現的,當執行 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 隨機讀寫,但是 Hbaseupdate 操作不是真的 update ,它的實際操作是 insert 一條新的資料,打上不同的 timestamp ,而老的資料會在有效期之後自動刪除。而 Clickhouse 乾脆就不支援 updatedelete

ClickHouse核心涉及模組

1. ColumnField

ColumnFieldClickHouse 資料最基礎的對映單元。作為一款百分之百的列式儲存資料庫, ClickHouse 按列儲存資料,記憶體中的一列資料由一個 Column 物件表示。 Column 物件分為介面和實現兩個部分,在 IColumn 介面物件中,定義了對資料進行各種關係運算的方法,例如插入資料的 insertRangeFrominsertFrom 方法、用於分頁的 cut ,以及用於過濾的 filter 方法等。而這些方法的具體實現物件則根據資料型別的不同,由相應的物件實現,例如 ColumnStringColumnArrayColumnTuple 等。在大多數場合, ClickHouse 都會以整列的方式操作資料,但凡事也有例外。如果需要操作單個具體的數值  也就是單列中的一行資料  ) ,則需要使用 Field 物件, Field 物件代表一個單值。與 Column 物件的泛化設計思路不同, Field 物件使用了聚合的設計模式。在 Field 物件內部聚合了 NullUInt64StringArray13 種資料型別及相應的處理邏輯。

2. DataType

資料的序列化和反序列化工作由 DataType 負責。 IDataType 介面定義了許多正反序列化的方法,它們成對出現,例如 serializeBinarydeserializeBinaryserializeTextJSONdeserializeTextJSON 等,涵蓋了常用的二進位制、文字、 JSONXMLCSVProtobuf 等多種格式型別。 IDataType 也使用了泛化的設計模式,具體方法的實現邏輯由對應資料型別的例項承載,例如 DataTypeStringDataTypeArrayDataTypeTuple 等。

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

3. BlockBlock

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

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

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

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

4. Table

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

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