Cozo事務(wù)型關(guān)系型數(shù)據(jù)庫
Cozo 是事務(wù)型關(guān)系型數(shù)據(jù)庫,使用 Datalog 作為查詢語言的高性能·關(guān)系型·可嵌入式·圖數(shù)據(jù)庫。
- 一個 可嵌入 的數(shù)據(jù)庫;
- 一個使用 Datalog 作為查詢語句的數(shù)據(jù)庫;
- 一個專注于 圖數(shù)據(jù)、圖算法 的數(shù)據(jù)庫;
- 一個可進行 歷史穿梭 查詢的數(shù)據(jù)庫;
- 一個支持 高性能、高并發(fā) 的數(shù)據(jù)庫。
“可嵌入”是什么意思?
如果某個數(shù)據(jù)庫能在不聯(lián)網(wǎng)的手機上使用,那它大概就是嵌入式的。舉例來說,SQLite 是嵌入式的,而 MySQL、Postgres、Oracle 等不是(它們是客戶端—服務(wù)器(CS)架構(gòu)的數(shù)據(jù)庫)。
如果數(shù)據(jù)庫與你的主程序在同一進程中運行,那么它就是 嵌入式 數(shù)據(jù)庫。與此相對,在使用 客戶端—服務(wù)器 架構(gòu)的數(shù)據(jù)庫時,主程序需要通過特定的接口(通常是網(wǎng)絡(luò)接口)訪問數(shù)據(jù)庫,而數(shù)據(jù)庫也可能運行在另一臺機器或獨立的集群上。嵌入式數(shù)據(jù)庫使用簡單,資源占用少,并可以在更廣泛的環(huán)境中使用。
Cozo 同時也支持以客戶端—服務(wù)器模式運行。因此,Cozo 是一個 可嵌入 而不是僅僅是 嵌入式 的數(shù)據(jù)庫。在客戶端—服務(wù)器模式下,Cozo 可以更充分地發(fā)揮服務(wù)器的性能。
“圖數(shù)據(jù)”有什么用?
從本質(zhì)上來說,數(shù)據(jù)一定是相互關(guān)聯(lián)、自關(guān)聯(lián)的,而這種關(guān)聯(lián)的數(shù)學表達便是 圖 (也叫 網(wǎng)絡(luò))。只有考慮這些關(guān)聯(lián),才能更深入地洞察數(shù)據(jù)背后的邏輯。
大多數(shù)現(xiàn)有的 圖數(shù)據(jù)庫 強制要求按照屬性圖(property graph)的范式存儲數(shù)據(jù)。與此相對,Cozo 使用傳統(tǒng)的關(guān)系數(shù)據(jù)模型。關(guān)系數(shù)據(jù)模型有存儲邏輯簡單、功能強勁等優(yōu)點,并且處理圖數(shù)據(jù)也毫無問題。更重要的是,數(shù)據(jù)的洞察常常需要挖掘隱含的關(guān)聯(lián),而關(guān)系數(shù)據(jù)模型作為關(guān)系 代數(shù)(relational algebra)可以很好地處理此類問題。比較而言,因為其不構(gòu)成一個代數(shù),屬性圖模型僅僅能夠?qū)@性的圖關(guān)系作為圖數(shù)據(jù)處理,可組合性很弱。
“Datalog”好在哪兒?
Datalog 1977 年便出現(xiàn)了,它可表達所有的 關(guān)系型查詢,而它與 SQL 比起來的優(yōu)勢在于其對 遞歸 的表達。由于執(zhí)行邏輯不同,Datalog 對于遞歸的運行,通常比相應(yīng)的 SQL 查詢更快。Datalog 的可組合性、模塊性都很優(yōu)秀,使用它,你可以逐層、清晰地表達所需的查詢。
遞歸對于圖查詢尤其重要。Cozo 使用的 Datalog 方言 叫做 CozoScript,其允許在一定條件下混合使用聚合查詢與遞歸,從而進一步增強了 Datalog 的表達能力。同時,Cozo內(nèi)置了圖分析中常用的一些算法(如 PageRank 等),調(diào)用簡單。
對 Datalog 有進一步了解以后,你會發(fā)現(xiàn) Datalog 的 規(guī)則 類似于編程語言中的函數(shù)。規(guī)則的一大特點是其可組合性:將一個查詢分解為多個漸進的規(guī)則可使查詢更清晰、易維護,且不會有效率上的損失。與此相對的,復(fù)雜的 SQL 查詢語句通常表達為多層嵌套的“select-from-where”,可讀性、可維護性都不高。
歷史穿梭?
在數(shù)據(jù)庫中,“歷史穿梭”的意思是記錄數(shù)據(jù)的一切變化,以允許針對某一時刻的數(shù)據(jù)進行執(zhí)行查詢,用來窺探歷史。
在某種意義上,這使數(shù)據(jù)庫成為 不可變 數(shù)據(jù)庫,因為沒有數(shù)據(jù)會被真正刪除。
每一項額外的功能都有其代價。如果不使用某個功能,理想的狀態(tài)是不必為這個功能的代價埋單。在 Cozo 中,不是所有數(shù)據(jù)表都自動支持歷史穿梭,這就把是否需要此功能、是否愿意支付代價的選擇權(quán)交到了用戶手里。
這個關(guān)于歷史穿梭的小故事可能啟發(fā)出一些歷史穿梭的應(yīng)用場景。
“高性能、高并發(fā)”,有多高?
我們在一臺 2020 年的 Mac Mini 上,使用 RocksDB 持久性存儲引擎(Cozo 支持多種存儲引擎)做了性能測試:
- 對一個有 160 萬行的表進行查詢:讀、寫、改的混合事務(wù)性查詢可達到每秒 10 萬次,而只讀查詢可達到每秒 25 萬次。在此過程中,數(shù)據(jù)庫使用的內(nèi)存峰值僅為50MB。
- 備份數(shù)據(jù)的速度為每秒約 100 萬行,恢復(fù)速度為每秒約 40 萬行。備份、恢復(fù)的速度不隨表單數(shù)據(jù)增長而變慢。
- 分析查詢:掃描一個有 160 萬行的表大約需要 1 秒(根據(jù)具體查詢語句大約有上下 2 倍以內(nèi)的差異)。查詢所需時間與查詢所涉及的行數(shù)大致成比例,而內(nèi)存使用主要決定于返回集合的大小。
- 對于一個有 160 萬個頂點,3100 萬條邊的圖數(shù)據(jù)表,“兩跳”圖查詢(如查詢某人的朋友的朋友都有誰)可在 1 毫秒內(nèi)完成。
- Pagerank 算法速度:1 萬個頂點,12 萬條邊:50 毫秒以內(nèi);10 個萬頂點,170 萬條邊:1 秒以內(nèi);160 萬個頂點,3100 萬條邊:30秒以內(nèi)。
更多的細節(jié)參見此文。
學習 Cozo
你得先安裝一個數(shù)據(jù)庫才能開始學,對吧?不一定:Cozo 是“嵌入式”的,所以我們直接把它通過 WASM 嵌入到瀏覽器里了!打開這個頁面,然后:
當然也可以一步到位:先翻到后面了解如何在熟悉的環(huán)境里安裝原生 Cozo 數(shù)據(jù)庫,再開始學習。
一些示例
通過以下示例,可在正式開始學習之前對 Cozo 的查詢先有個感性認識。
假設(shè)有個表,名為 *route,含有兩列,名為 fr 和 to,其中數(shù)據(jù)為機場代碼(如 FRA 是法蘭克福機場的代碼),且每行數(shù)據(jù)表示一個飛行航線。
從 FRA 可以不轉(zhuǎn)機到達多少個機場:
?[count_unique(to)] := *route{fr: 'FRA', to}
| count_unique(to) |
|---|
| 310 |
從 FRA 出發(fā),轉(zhuǎn)機一次,可以到達多少個機場:
?[count_unique(to)] := *route{fr: 'FRA', to: 'stop},
*route{fr: stop, to}
| count_unique(to) |
|---|
| 2222 |
從 FRA 出發(fā),轉(zhuǎn)機任意次,可以到達多少個機場:
reachable[to] := *route{fr: 'FRA', to}
reachable[to] := reachable[stop], *route{fr: stop, to}
?[count_unique(to)] := reachable[to]
| count_unique(to) |
|---|
| 3462 |
從 FRA 出發(fā),按所需的最少轉(zhuǎn)機次數(shù)排序,到達哪兩個機場需要最多的轉(zhuǎn)機次數(shù):
shortest_paths[to, shortest(path)] := *route{fr: 'FRA', to},
path = ['FRA', to]
shortest_paths[to, shortest(path)] := shortest_paths[stop, prev_path],
*route{fr: stop, to},
path = append(prev_path, to)
?[to, path, p_len] := shortest_paths[to, path], p_len = length(path)
:order -p_len
:limit 2
| to | path | p_len |
|---|---|---|
| YPO | ["FRA","YYZ","YTS","YMO","YFA","ZKE","YAT","YPO"] |
8 |
| BVI | ["FRA","AUH","BNE","ISA","BQL","BEU","BVI"] |
7 |
FRA 和 YPO 這兩個機場之間最短的路徑以及其實際飛行里程是多少:
start[] <- [['FRA']]
end[] <- [['YPO]]
?[src, dst, distance, path] <~ ShortestPathDijkstra(*route[], start[], end[])
| src | dst | distance | path |
|---|---|---|---|
| FRA | YPO | 4544.0 | ["FRA","YUL","YVO","YKQ","YMO","YFA","ZKE","YAT","YPO"] |
當查詢語句有錯時,Cozo 會提供明確有用的錯誤信息:
?[x, Y] := x = 1, y = x + 1
eval::unbound_symb_in_head × Symbol 'Y' in rule head is unbound ╭──── 1 │ ?[x, Y] := x = 1, y = x + 1 · ─ ╰──── help: Note that symbols occurring only in negated positions are not considered bound
更多信息請參見軟件網(wǎng)址及文檔。
