本文翻譯自官網:Flink Table Api & SQL 動態表 https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/table/streaming/dynamic_tables.html
SQL和關系代數在設計時並未考慮流數據。所以,關系代數(和SQL)與流處理之間在概念上有一些差距。
本頁討論了這些差異,並說明了Flink如何在無界數據上實現與常規數據庫引擎在有界數據上相同的語義。
數據流上的關系查詢
下表針對輸入數據、執行和輸出結果,比較了傳統的關系代數和流處理之間的差異。
關系代數/ SQL | 流處理 |
---|---|
關系(或表)是有界的(多個)元組。 | 流是無限的元組序列。 |
對批處理數據(例如,關系數據庫中的表)執行的查詢可以訪問完整的輸入數據。 | 流查詢在啟動時無法訪問所有數據,而必須“等待”以流式傳輸數據。 |
批處理查詢產生固定大小的結果后終止。 | 流查詢根據接收到的記錄不斷更新其結果,並且永遠不會完成。 |
盡管存在這些差異,使用關系查詢和SQL處理流並不是不可能的。先進的關系數據庫系統提供了稱為“ 物化視圖”的功能(將視圖的結果作為表存儲起來,並定時更新,Oracle 有,百度 Oracle 物化視圖)。物化視圖被定義為SQL查詢,就像常規虛擬視圖一樣。 與虛擬視圖相反,物化視圖緩存查詢結果,以便在訪問視圖時無需評估查詢。 緩存的一個常見挑戰是防止緩存提供過時的結果。 修改其定義查詢的基表時,物化視圖將過時。 Eager View Maintenance 是一種在其基表更新后立即更新物化視圖的技術。
如果考慮以下因素,那么Eager View Maintenance 和對流進行 SQL查詢之間的聯系將變得顯而易見:
- 數據庫表是一個結果流的
INSERT
,UPDATE
和DELETE
DML語句,通常被稱為更新日志流。 - 物化視圖定義為SQL查詢。為了更新視圖,查詢會連續處理視圖基本關系的變更日志流。
- 實例化視圖是流式SQL查詢的結果。
考慮到這些要點,我們將在下一節介紹動態表的以下概念。
動態表和持續查詢
動態表是 Flink 的 Table API 和 SQL 對流數據支持的核心概念。與代表批處理數據的靜態表相反,動態表隨時間而變化。可以像靜態批處理表一樣查詢它們。查詢動態表會產生一個持續查詢。持續查詢永遠不會終止並產生動態表作為結果。查詢不斷更新其(動態)結果表以反映其(動態)輸入表上的更改。本質上,對動態表的持續查詢與定義物化視圖的查詢非常相似。
重要的是要注意,持續查詢的結果在語義上始終等效於在輸入表的快照上以批處理方式執行的同一查詢的結果。
下圖顯示了流,動態表和持續查詢之間的關系:

- 流將轉換為動態表。
- 在動態表上評估持續查詢,生成新的動態表。
- 生成的動態表將轉換回流。
注意:動態表首先是一個邏輯概念。在查詢執行過程中不一定(完全)實現動態表。
在下文中,我們將通過具有以下模式的單擊事件流來解釋動態表和持續查詢的概念:
[ user: VARCHAR, // the name of the user cTime: TIMESTAMP, // the time when the URL was accessed url: VARCHAR // the URL that was accessed by the user ]
在流上定義表
為了使用關系查詢處理流,必須將其轉換為Table
。從概念上講,流的每個記錄都被解釋為INSERT
對結果表的修改。本質上,我們是從僅 INSERT的 changelog 流中構建表。
下圖可視化了點擊事件流(左側)如何轉換為表格(右側)。隨着插入更多點擊流記錄,結果表將持續增長。

注意:在流上定義的表在內部未實現。
持續查詢
在動態表上評估持續查詢,並生成一個新的動態表作為結果。與批處理查詢相反,持續查詢永遠不會終止並根據其輸入表的更新來更新其結果表。在任何時間點,持續查詢的結果在語義上均等同於在輸入表的快照上以批處理模式執行同一查詢的結果。
在下面的示例中,我們顯示了對單擊事件流中定義的表的兩個示例查詢。
第一個查詢是一個簡單的GROUP-BY COUNT
聚合查詢。它將clicks
表中的字段 user 分組,並計算訪問的URL數量。下圖顯示了隨着
clicks
表中其他行的更新,隨着時間的推移如何評估查詢。

啟動查詢后,clicks表(左側)為空。 當第一行插入到clicks表中時,查詢開始計算結果表。插入第一行[Mary,./home]后,結果表(右側,頂部)由單行[Mary,1]組成。將第二行[Bob,./cart]插入到clicks表中時,查詢將更新結果表並插入新行[Bob,1]。 第三行[Mary,./prod?id=1]產生已計算結果行的更新,從而將[Mary,1]更新為[Mary,2]。 最后,當第四行附加到clicks表時,查詢將第三行[Liz,1]插入結果表。
第二個查詢與第一個查詢類似,但是在對clicks
表進行計數之前,除了將user
屬性歸類之外,表還在小時滾動的窗口中進行分組(在基於URL的計數之前,基於時間的計算(例如,窗口基於特殊的時間屬性),稍后將進行討論) )。同樣,該圖顯示了在不同時間點的輸入和輸出,以可視化動態表的變化過程。

和以前一樣,輸入表clicks
顯示在左側。該查詢每小時持續計算結果並更新結果表。clicks 表包含四行,其時間戳(cTime)在 12:00:00 和 12:59:59 之間。該查詢從輸入計算出兩個結果行(每個用戶一個),並將它們附加到結果表中。對於 13:00:00
和 13:59:59 之間的下一個窗口,該clicks
表包含三行,這將導致另外兩行追加到結果表中。結果表將更新,因為clicks
隨着時間的推移會添加更多行。
更新和 Append 查詢
盡管兩個示例查詢看起來非常相似(都計算分組計數匯總),但是它們在一個重要方面有所不同:
- 第一個查詢更新先前發出的結果,即結果表包含
INSERT
和UPDATE
更改的變更日志流。 - 第二個查詢僅附加到結果表,即結果表的changelog流僅包含
INSERT
更改。
查詢是生成僅追加表還是更新表具有一些含義:
- 產生更新更改的查詢通常必須維護更多狀態(請參閱以下部分)。
- 僅追加表到流的轉換與更新表的轉換不同(請參閱表到流轉換部分)。
查詢限制
可以將許多但不是全部的語義有效查詢評估為流中的持續查詢。某些查詢由於需要維護的狀態大小或計算更新過於昂貴(注:計算成本)而無法計算。
- 狀態大小:持續查詢是在無限制的流上評估的,通常應該運行數周或數月。因此,持續查詢處理的數據總量可能非常大。必須更新先前發出的結果的查詢需要維護所有發出的行,以便能夠更新它們。例如,第一個示例查詢需要存儲每個用戶的URL計數,以便能夠增加計數並在輸入表接收到新行時發出新結果。如果僅跟蹤注冊用戶,則要維護的計數數量可能不會太高。但是,如果未注冊的用戶獲得分配的唯一用戶名,則要維護的計數數量將隨着時間的推移而增加,並最終可能導致查詢失敗。
SELECT user, COUNT(url)
FROM clicks
GROUP BY user;
- 計算更新:即使只添加或更新一條輸入記錄,某些查詢也需要重新計算和更新很大一部分發射結果行。顯然,這樣的查詢不太適合作為持續查詢執行。下面的查詢是一個示例,該查詢根據最終點擊的時間為每個用戶計算 排名。
clicks
表格收到新行后,lastAction
用戶的身份將更新,並且必須計算新排名。但是,由於兩行不能具有相同的排名,因此所有排名較低的行也需要更新。
SELECT user, RANK() OVER (ORDER BY lastLogin)
FROM (
SELECT user, MAX(cTime) AS lastAction FROM clicks GROUP BY user
);
“ 查詢配置”章節討論了用於控制持續查詢的執行的參數。某些參數可用於權衡維護狀態的大小以提高結果的准確性。
表到流的轉換
動態表可以通過INSERT
,UPDATE
以及DELETE不斷修改,
就像一個普通的數據庫表。它可能是具有單行的表,該表會不斷更新;可能是一個僅插入的表,沒有UPDATE
和DELETE
修改,或者介於兩者之間。
將動態表轉換為流或將其寫入外部系統時,需要對這些更改進行編碼。Flink的Table API和SQL支持三種方式來編碼動態表的更改:
-
Append-only流:可以通過發出插入的行將僅通過INSERT更改修改的動態表轉換為流。
-
Retract 流:撤回流是具有兩種消息類型的流,添加消息和撤回消息。通過將INSERT更改編碼為添加消息,將DELETE更改編碼為撤會消息,將UPDATE更改編碼為更新(先前)行的更新消息,並將更新消息編碼為更新(新)行,將動態表轉換為撤回流。下圖可視化了動態表到撤回流的轉換。

- Upsert 流:Upsert流是具有兩種消息類型的流,Upsert 消息和Delete消息。轉換為upsert流的動態表需要一個(可能是復合的)唯一鍵。通過將INSERT和UPDATE更改編碼為upsert消息並將DELETE更改編碼為delete消息,將具有唯一鍵的動態表轉換為流。流消耗操作員需要知道唯一鍵屬性,以便正確應用消息。流消費算子需要知道唯一鍵屬性,以便正確應用消息。 撤回流的主要區別在於UPDATE更改使用單個消息進行編碼,因此效率更高。下圖可視化了動態表到 upsert流的轉換。

在Common Concepts頁面上討論了將動態表轉換為DataStream的API。請注意,將動態表轉換為DataStream時僅支持添加和撤消流。在TableSources和TableSinks頁面上討論了向外部系統發送動態表的TableSink接口。
歡迎關注Flink菜鳥公眾號,會不定期更新Flink(開發技術)相關的推文