作者:孫宇熙 & 張建松
很顯然,我們正處于一個大數據的時代。互聯網和移動互聯網絡的快速發展帶來了數據產生速率的極大增長。每天、每時每刻都有數以十億量級的設備(有人預計在 2030 年前,會有超過千億量級的聯網傳感設備)在產生巨大體量的數據。
數據庫是被人們創造出來解決這種不斷增長的數據挑戰的利器。還有其它類似的概念、工具和解決方案,例如數據倉庫、數據集市、數據湖泊等等,來解決我們日常所面對的數據存儲、數據轉換、數據分析、匯總、報表等等一系列工作。但是,是什么讓數據庫變的如此重要,以至于我們對其難以割舍呢?筆者以為,有兩件事情是數據庫的核心“競爭力”:
- 性能(Performance)
- 查詢語言(Query Language)
在現代商業社會中,性能應該是“一等公民”。一個數據庫之所以可能被稱作是數據庫,意味著它可以在合理的,通常是最小的,至少是符合商業訴求的延遲內來完成規定的工作。這一點上,數據庫和商務智能時代的很多數據倉庫或 Hadoop 大數據陣營的解決方案顯得很不相同,后者或許有著很好的分布式、可擴展的能力,但是他們的性能絕不是“令人驕傲”的優勢之一。這或許可以解釋為什么我們在近年看到了市場上關于 Hadoop 已經死亡的一些看法,至少越來越多的商業落地場景中,基于內存計算的 Spark 和其它一些新型的基礎架構正在不斷取代 Hadoop。
注:遺憾的是,以關系型數據庫為代表的傳統數據庫架構中,計算是附著于存儲引擎而存在的。只有存儲引擎是一等公民,計算只是二等公民。連 SQL 的 Stored Procedure 都被叫做”存儲進程“。翻開任何一本教科書,只有存儲引擎的章節,而不會有獨立的”計算引擎“的章節。隨著大數據、快數據時代的來臨,SQL 的這一套架構已經越發顯得不合時宜了。
從大數據向快數據(Fast Data)轉型或遷移的趨勢是為了做到讓底層的數據庫框架可以應對不斷增長的數據規模,而不至于犧牲數據處理性能(數據吞吐率)。下圖中展示了以數據庫為中心的數據處理基礎架構和技術的進化路徑。需要指出的是,在這個過程中有一些技術和產品是”反智“的,最典型的莫過于 Hadoop 生態和一些無法提供高性能計算的所謂的”水平分布式數據庫“系統。這兩類反智系統的最大特點都是可以解決 IO 密集型操作,但是無法解決計算密集型挑戰。簡而言之,就是它們宣稱用大量的低配置的服務器來實現了更大體量的數據存儲,但是一定沒有高性能計算的能力!包括很多云平臺也有類似的虛假宣傳——100 臺低配的機器,或許能比 10 臺高配的機器提供更大的存儲空間,但是計算能力(特別是復雜類型的查詢)反而變差了很多!打個比方,1 臺物理機如果被虛擬化成 3 臺虛擬機,這 3 臺虛擬機的計算和存儲能力都受限于底層的那臺物理機!而現在很多所謂的水平分布式系統所宣傳的就是 3 臺虛擬機碾壓 1 臺物理機!

Diagram-0:從數據到大數據,到快數據再到深數據的進化路線
查詢語言是計算機編程語言出現以來被發明出來的最好的事物之一。我們希望計算機程序可以有著人類一樣的對數據的智能化的、深度的篩選能力,但是這種強人工智能的訴求目前為止還從來沒有被真正實現過。退而求其次,聰明的程序員們、語言學家們通過創建人造的計算機編程語言來幫助把人類的意圖、指令實現,而數據庫中的查詢語言就是這種可以面向數據的“半智能化的”數據處理語言。
在我們準備進入眾所周知的 SQL 的世界之前,讓我們來先概覽一下非關系型數據庫(例如 NoSQL 等)所支持的查詢語言的一些特性。
首先,我們來看一下鍵值數據庫(KV Store),常見的鍵值庫的實現有 Berkeley DB、Level DB 等等。技術上而言,鍵值庫并不支持或使用確切的查詢語言,這是因為它之上所支持的操作實在是太簡單了,使用簡單的 API 就已經足夠了!典型的鍵值庫支持三種操作:
- 插入(Insert)
- 讀取(Get)
- 刪除(Delete)
你可能會質疑說 Cassandra 支持 CQL(英文全稱為 Cassandra Query Language)。沒有問題,Cassandra 實際上是一種寬列庫(wide-column store),我們可以把它看做是一種二維的鍵值庫。對于 Cassandra 所支持的數據操作復雜度極高且高度分布式的架構而言,提供一層抽象查詢語言來減少程序員的工作負擔是絕對具有其正面意義的——否則程序員就需要記住那些復雜的 API 調用函數的各種參數集合。另外,CQL 使用了與常規 SQL 類似的概念,例如表、行、列等概念——我們后續會更多的討論 SQL 相關的議題。
對于 Apache Cassandra 的一個批評,或許可以看做是對 NoSQL 數據庫的整體而言的,是 CQL 并不支持 SQL 中常見的 JOIN(表連接)操作。很顯然,這種批評的思路是深深的根植于 SQL 的思維方式之中的—— JOIN 是個雙刃劍,它解決了一件事情的同時,也帶來了一個問題,比如(巨大的)性能損耗。事實上,Cassandra 可以支持 JOIN 操作,通過以下兩種解決方案:
- SPARK SQL:Apache Spark’s Spark SQL with Cassandra
- ODBC 驅動:Cassandra with ODBC driver

Diagram-1:在 Spark 結構化數據架構上的 Spark SQL 查詢語言
然而,這些解決方案并不是沒有代價的,例如 ODBC 在面向大數據集或集群化操作時性能是個問題。而 Spark SQL 則是在 Cassandra 基礎上面又疊加一層新的系統復雜度,對于多系統維護、系統穩定性,以及不少程序員痛恨了解新系統、學習新知識而言,都是個挑戰。
關于 Apache Spark 和 Spark SQL 不得不多說兩句,Spark 設計之初就是要應對 Apache Hadoop 的低效性和低性能。Hadoop 從谷歌的 GFS(谷歌文件系統)以及 Map-Reduce 理念及實現中借鑒了兩個關鍵概念,并由此而創造了 HDFS(Hadoop 分布式文件系統)以及 Hadoop MapReduce;Spark 則相當于利用了分布式共享內存架構實現了 100 倍的相對于 Hadoop 而言的性能提升。打一個比方,僅僅是簡單把數據從硬盤移入到內存中來處理,你就會獲得 100 倍左右的性能提升,因為內存的吞吐率就是硬盤的 100 倍之高,證明完畢。Spark SQL 是提供了一個 SQL 兼容的查詢語言接口來支持訪問 Spark 中的結構化數據,相比于 Spark 原生的 RDD API 而言,Spark SQL 已經算作是一種進步了——如前所述,如果 API 變得過于復雜的時候,對查詢語言的訴求就變得越來越強烈了,因為它更加靈活、實用、強大!
注:筆者在十幾年前 Yahoo! 戰略數據部(SDS)就職期間,正是 Yahoo! 在孵化 Hadoop 項目的時期,相比 SDS 的其它高性能、分布式海量數據處理框架實現而言,Hadoop 的性能已經是在內部眾所周知的不盡如人意,筆者印象最深刻的就是很多 Linux 中常見的排序、搜索等工具都被改寫為可以以極高的性能來應對海量數據的處理,例如 sort 命令的性能被提升 100+ 倍來應對 GB 到 TB 量級的大數據集的排序挑戰。或許這也能解釋為什么 Hadoop 后來被 Yahoo! 捐獻給了 Apache 開源社區,而其它顯然更具備性能優勢的項目卻沒有被開源。或許讀者要思考:開源的就是最好的嗎?

Diagram-2:數據庫和數據處理技術的演進歷程
比照 Diagram-0 中提及的整個業界的數據處理技術的演化(從數據→大數據→快數據→深數據),對應的底層的數據處理技術就會從關系型數據庫主導的時代向非關系型數據庫(例如 NoSQL/Spark 等)、NewSQL 和圖數據庫框架演進!Diagram-2 中列出了這種趨勢和演化路徑——我們可以大膽的預判未來的核心數據處理基礎架構一定是至少包含或由圖架構主導的。
在過去半個世紀中的數據庫技術的進化或可通過 Diagram-3 來概括,一言以蔽之:從 70 年代之前的導航型數據庫,到 70-80 年代開始興起的關系型數據庫,到 90 年代的 SQL 編程語言的興起,到 21 世紀第一個 10 年后出現的各種 NoSQL(NoSQL = Not Only SQL,不僅僅是SQL!)——或許下面這張圖留給我們一個迷思,圖查詢語言會是終極的查詢語言嗎?
換個角度問個問題:人類終極的數據庫是什么?或許你不會反對這個答案:人腦!人類的大腦到底是什么樣的數據庫技術?筆者以為是圖數據庫,至少在概率上它遠遠高于關系型數據庫、列數據庫、鍵值庫、文檔數據庫或時序數據庫。或許是我們還沒有發明的一種數據庫。但是圖數據庫是最接近終極數據庫的,毋庸置疑。對于這一點充滿質疑的讀者,建議閱讀之前的一篇文章《圖數據結構的進化》,希望有所幫助。

Diagram-3:進化的視角看數據庫和查詢語言的進化歷史
如果讀者對于 SQL 語言的演進有所了解,它直接的推動了關系型數據庫的崛起(如果諸位還能回憶起在 SQL 語言出現之前的數據庫的使用是如何的笨拙與痛苦的話)。互聯網的崛起催生了NoSQL的誕生和崛起,其中很大的一個因素是關系型數據庫沒辦法很好的應對數據處理速度、數據建模靈活性的訴求。
NoSQL 數據庫一般而言被分為以下 4 或者 5 大類,每一類都有其各自的特性:
- 鍵值(Key-Value):性能和簡易性(Performance and simplicity)
- 寬列(Wide-Column):體量與性能(Volume and performance)
- 文檔(Document):數據多樣性(Variety of data structures)
- 圖(Graph): 深數據+快數據(Deep-data and fast-data)
- (可選的)時序(Time Series):IOT 數據、時序優先性能(Performance for time-stamped/IOT data)
從查詢語言(或者 API)的復雜度的角度來看,數據處理能力也存在著一條進化路徑。如下圖 Diagram-4 所示,形象的表達了 NoSQL 類數據庫和以 SQL 為中心的關系型數據庫之間的比對:
- 相對原始的鍵值庫 API → 有序鍵值庫(Ordered Key-Value)
- 有序鍵值庫 → 大表(Big-Table 是一種典型的 Wide Column 庫)
- 列數據庫 → 文檔數據庫(例如 MongoDB),并帶有全文搜索能力(搜索引擎!)
- 文檔數據庫 → 圖數據庫
- 從圖數據庫 → SQL 中心化的關系型數據庫。

Diagram-4:數據庫查詢語言的進化(一家之言)
上圖中,SQL 被認為是最先進的數據處理與查詢語言。但是,這個觀點是 20 年前的!隨著圖數據庫技術的推陳出新,我們已經可以清晰的看到 SQL/RDBMS 在逐步走向衰敗,而 GQL 和 GraphDB 會逐步成為下一代的主流數據庫。
在這里我們需要稍微深入的研究一下 SQL 的演化歷史,來幫助我們有個更全局化的觀念。
SQL 的出現已經將近 50 年了,并且已經迭代了很多版本(平均每 3-4 年一次大迭代),其中最知名的非 SQL-92、99 莫屬,例如在 92 版本中在 FROM 語句中增加了子查詢(subquery)功能;在 99 年版本中增加了 CTE(Common Table Expression)功能。這些功能極大的增加了關系型數據庫的靈活性(如下圖所示)。


Diagram-5: Subquery per SQL-92 and CTE per SQL-99
然后,關系型數據庫始終存在一個“弱點”,那就是對于遞歸型數據結構的支持(Recursive data structures)。所謂遞歸數據結構,指的是有向關系圖的功能實現。令人感到諷刺的是,關系型數據庫的名字雖然包含了關系,但是它在設計伊始就很難支持關聯關系的查詢。為了實現關聯查詢,關系型數據庫不得不依賴表連接操作——每一次表連接都意味著潛在的表掃描操作,以及因為”笛卡爾積“問題導致的性能指數級下降(以及 SQL 代碼的復雜度、維護難度的直線上升)!
表連接操作的性能損耗是直接源自于關系型數據庫的基礎設計思想的:
- 數據正則化(Data Normalization)
- 固定化的、預先設定的表模式(Fixed/Predefined Schema)
如果我們看一下 NoSQL 中的核心理念,在數據建模中突出了數據去正則化。所謂數據去正則化,指的是用空間換取時間(犧牲空間來換取更高的性能!),在 NoSQL(也包括 Hadoop 等,例如典型的 3、5、7 份拷貝的理念)中,數據經常被以多份拷貝的方式存儲 ,而這樣做的好處在于數據可以以緊鄰計算資源的方式被處理。這種理念和 SQL 中的只存一份正則化設計思路是截然相反的——后者或許可以節省一些存儲空間,但是對于復雜的 SQL 操作而言,帶來的是性能的損耗。
預先定義數據的表模式的理念是 SQL 與 NoSQL 的另一大差異。對于初次接觸這一概念的讀者而言,理解這個點會有些復雜,從下面這個角度去理解或許更直觀:
模式第一,數據第二 vs. 數據先行,模式第二
Schema First, Data Second vs. Data First, Schema Second
在關系型數據庫中,系統管理員(DBA),需要先定義表的結構(schema),然后才會加載第一行數據進入數據庫,他不可能動態的更改表的結構。這種僵化性對于固定模式、一成不變的數據結構和業務需求而言或許不是什么大問題。但是,讓我們想象一下如果數據模式可以自我調整,并能根據流入的數據動態調整,這就給了我們極大的靈活性。對于強 SQL 背景的人而言,這是很難被想象的。但是,如果我們暫時拋棄掉我們的僵化的、限制性的思維,取而代之以一種成長性的思維方式 – 我們所要達成的目標是一種“ Schema-Free 或 Schemaless ”的數據模式,也就是無需預先設定數據模式,數據之間的關聯性不需要預先定義和了解,隨著數據的流入,它們會自然的形成某種關聯關系 – 而數據庫所需要做的是對應著這些數據“因地制宜”的來處理如何查詢與計算。
在過去幾十年中,數據庫程序員已經被訓練得一定要先了解數據模型,不論它是關系型表結構還是實體 E-R 模式圖。了解數據模型當然有它的優點,然而,這也讓開發流程變得更加復雜和緩慢。如果讀者們還記得上一次你參與的交鑰匙解決方案的開發周期有多長了?一個季度、半年、一年還是更久?在一個有 8000 張表的 Oracle 數據庫中,沒有任何一個 DBA 可以完全掌握所有表之間的關聯關系 – 這個時候,我們更愿意把這套脆弱的系統比作一個定時炸彈,而你的所有業務都綁定在其上!
關于無模式(Schema-Free),我們并不想解釋文檔型數據庫或者寬列據庫,盡管它們都多少有一些和圖數據庫相似的設計理念。我們在下文中會用一些具體的圖數據庫上的例子來幫助讀者理解無模式意味著什么。
在圖數據庫中,邏輯上只有兩類基礎的數據類型:
- 頂點(Nodes 或 Vertices)
- 邊(Edges)
一個頂點具有它自己的 ID 和屬性(標簽、類別及其它屬性)。邊也類似,除了它通常是由兩個頂點的順序決定的(所謂有向圖的概念指的是每條邊由一個初始頂點對應一個終止頂點,再加上其它屬性所構成,例如邊的方向、標簽、權重等等)。除了這點兒基礎的數據結構,圖數據庫并不需要任何預先定義的模式或表結構!這種極度簡化的理念恰恰和人類如何思考以及存儲信息有著很大的相似性——我們通常并不在腦海中設定表結構,我們隨機應變!!!
現在,讓我們看一看一些真實世界場景中的圖數據庫實現,例如下圖(Diagram-6)中的一個典型的圖數據集中的頂點的屬性定義,它包含了最初的幾個字段的定義例如 desc、level、name、type,但是也存在一些動態生成、擴增的字段,例如#cc、#pr、#khop_1等等。如果比照著關系型數據庫而言,整個表的結構是動態可調整的。

Diagram-6:圖數據集中的頂點屬性(動態屬性)
注意上圖中的 name 和 type 字段的屬性為 STRING 類型,它可以最大化兼容廣譜的數據類型,進而提供最大化的靈活性。頂點之間如何產生關聯也無需被預先定義,這樣所形成的關聯網絡也是靈活的。
細心的讀者一定會問到,這種靈活性怎么來實現和保證性能優化呢?常識的做法是通過存儲與計算分層來實現,例如為了實現極佳的計算性能,數據可以動態的加載進入內存計算(LTE vs. UFE = Load-to-Engine vs. Unload-from-Engine),當然內存計算只是一部分,支持并發計算的數據結構也是必不可少的,有興趣的讀者可以參考之前的一篇文章《圖數據結構之進化》。
鍵值庫可以被看做是前-SQL(Pre-SQL 在這里表達的是一種相對于 SQL 而言更原始的特型)的非關系型架構庫,圖數據庫則可被看做是后 SQL(Post-SQL 在這里表達了一種相對而言的先進性)時代的,真正意義上支持遞歸式數據結構的數據庫。今天的不少 NoSQL 數據庫都試圖通過兼容 SQL 來獲得認可,但是在筆者看來,SQL 的設計理念是極度表限定的,所謂“表限定”(table-confined),指的是它的整個理念都是限定在二維世界中的,當要進行表連接操作時,就好比要去進入到三維或更高維的空間進行操作,而這也是時常低效和反直覺的,這是基于關系型數據模式的 SQL 本身的低維屬性決定的。
圖數據庫天然是高維的(除非它的實現是基于關系型數據庫或列數據庫實現的,那么本質上這種非原生圖的設計依然是低維驅動的,它的效率怎么可能會很高呢?),圖上面的操作天然的是遞歸式的,例如廣度優先搜索或深度優先搜索。當然,僅僅從語言的兼容性而言,圖上面一樣也可以支持SQL類操作來保持向關系型用戶群的習慣兼容,就像 Spark SQL 或 CQL 一樣,無論它的意義到底有多大或多長久。
下面,讓我們一起來看一些通過UQL(贏圖查詢語言)實現的圖上的查詢功能。同時,請仔細思考用 SQL 或是其它 NoSQL 數據庫將如何才能完成同樣的任務?
- 從某個頂點出發,找到它的第 1 到第 K 層(跳)的所有鄰居并返回。
UQL是與嬴圖高并發實時圖數據庫匹配的查詢語言。除了明顯的性能優勢以外,UQL的另外一個重要特點是高易用性,容易掌握,并有貼近自然語言的易讀性。UQL可以通過嬴圖Manager、嬴圖CLI 或嬴圖SDK/API 的接口調用。實現上面的查詢,在UQL中只需要 1 句話就可以完成。

Diagram-7:通過 spread() 操作對聯通子圖進行遍歷
一句話UQL:
spread().src(123).depth(6).spread_type(“BFS”).limit(4000);
上面的語句簡單易懂,基本上不需要太多解釋,調用 spread() 函數,從頂點 123 出發,搜索深度為 6 層,以 BFS 的方式進行搜索,限定返回最多 4000 個頂點(以及關聯的邊)。在 Diagram-7 圖中,紅色的小點就是起始頂點,通過以上語句操作的全部返回的頂點和邊所形成的子圖直接顯示在嬴圖的 WEB 界面上了。事實上,spread() 這個操作相當于允許從任何頂點出發找到它的聯通子圖——或者說它的鄰居網絡的形態可以被直接計算出來,并通過可視化界面直觀的展示出來。用這種方式也可以看出生成的聯通子圖中的頂點和邊所構成的熱點、聚集區域等圖上的空間特征——而并不需要傳統數據庫中的 E-R 模型圖。
- 給定的多個頂點,自動組網(形成一張頂點間相互聯通的網絡)
本查詢相對于熟悉傳統數據庫的讀者來說或許就顯得過于復雜了,用 SQL 也許無法實現這個組網功能。但是,對于人的大腦而言,這是個很天然的訴求——當你想在張三、李四、王五和趙六之間組成一張關聯關系的網絡的時候,你已經開始在腦海里繪制下面這張圖了。

Diagram-8:在嬴圖中自動生成網絡(子圖)
很顯然,UQL傾向于繼續使用 1 句話來實現這個“不可能”的操作:
autonet().srcs(12,21,30,40,123).depth(4).limit(5)
autonet() 就是我們調用的主要函數,它的名字已經非常直白了——自組網操作。你只需要提供一組頂點的 ID 信息,組網搜索的深度(4 層 = 4 跳),任意兩個頂點間的路徑數量限制為 5。下面,我們來從純數學的角度來分析一下,這個組網操作的計算復雜度:
可能返回路徑數量:C(5, 2) * 5 = (5 * 4 / 2) * 5 = 50 條
預估圖上計算復雜度: 50 paths * (E/V)4 = 50 * 256 = 12800
注:我們假設圖中的 (邊數/頂點數) 比例 = 4(平均值),也就是 E/V=4,搜索深度為4的時候每條路徑需要平均計算 256(4**4)次。
這個查詢在現實世界的應用中意義非同凡響。例如執法機關會根據電話公司的通話記錄來跟蹤多名嫌疑人的通話所組成的深度網絡的特征來判斷是否有其它嫌疑人牽連期間,犯罪集團是否存在某種異動,或者任意個數的嫌疑人構成的犯罪組織(crime ring)間的微妙的聯動關系等。
傳統大數據技術框架之上,這種多節點的組網操作極為復雜,甚至是沒有可能完成的任務。原因是因為計算復雜度太高 ,對于計算資源的需求太高,在短時間內沒有可能完成,或者是以 T+7(亦或 T+15、T+30)的方式實現,等到結果出來的時候,嫌疑人早已經逃之夭夭或者罪案已經發生良久了。假設有 1000 個嫌疑人需要參與組網,他們之間形成的網絡的路徑至少有 50 萬條(1,000 * 999) / 2)。如果查詢路徑深度為 6 層,如上所述,這個計算復雜度是 20 億次(假設 E/V=4,實際上 E/V 可能 >=10,那么計算次數可能達到 50 萬億次)。基于 Spark 架構的計算平臺可能需要數天來完成運算。利用嬴圖數據庫,該操作是以實時到近實時(T+0)的方式完成的,我們在不同的數據集上做過性能評測,嬴圖的性能至少是 Spark 框架的幾百倍到數千倍——如果原來需要 Spark 系統 1 天完成的計算,嬴圖僅需數秒、數分鐘!當與罪案斗爭的時候,每一秒都很寶貴。

Diagram-9:基于嬴圖的實時大規模的組網操作
對于像嬴圖這樣的實時高并發圖數據庫,性能肯定是“第一等公民”,但是這并沒有讓我們把語言的簡潔、直觀、易懂性當做次等公民。絕大多數人會發現UQL是如此的簡單,掌握了最基本的語法規則后,通常閱讀操作手冊幾分鐘到 30 分鐘內,就可以開始寫出你自己的UQL查詢語句了。
UQL借鑒并采用了鎖鏈式查詢(chain-query)的語言風格,對于熟悉文檔型數據庫 MongoDB 的讀者而言,上手UQL就更加簡單了。例如,一個簡單的鏈式路徑(點到點)查詢語句:

上面的例子中是去查詢兩個頂點間深度為 5 度的路徑,限定返回 5 條路徑,并且返回匹配的屬性“name”(通常是頂點或邊的名稱屬性)。
我們再來看一個稍微復雜一點的例子,模板查詢,當然,它所完成的功能也更加的強大。例如下面的例子中 t() 代表調用模板查詢調用,t(a) 表達的是為當前模板設定一個別名為 a,從頂點 12 開始,經過一條邊抵達到屬性 age 值為 20 的頂點 b(別名),返回這個模板所匹配的結果 a 和抵達頂點 b 的名字。和傳統 SQL 類似的地方是可以對任何過濾條件設置別名,和 SQL 不同的地方是,當異構的結果 a 和 b.name 一同被返回的時候,a 表達的是整個模板搜索所對應的路徑結果的集合,而 b.name 則是一組頂點的屬性的數組集合(如下圖所示)。這種異構靈活性是 SQL 不具備的。


Diagram-9-A:圖數據庫查詢中返回的異構結果集
下面我們再用一個例子來說明在圖查詢中使用簡單的查詢語言實現深度的、遞歸式的查詢:
t(a1).n(n1{age:20}).e(e1{rank:{$bt:[20,30]}})[3:7].n(n2).limit(50).
return(a1, n1, e1, n2._id, n2.name)
這個語句中,從年齡 =20 歲的頂點(可能有多個)出發,進行深度為 3-7 層的路徑搜索查詢抵達某些頂點,并且路徑中每條邊的權重介于 20-30 之間,找到 50 條路徑,并返一系列異構的數據(模板匹配的路徑本身、起始頂點、邊、終止定點的兩個屬性)。這種靈活度在 SQL 當中,如果不通過書寫大量的封裝代碼是很難實現的,而且這種搜索深度也是令關系型數據庫望而卻步的——通常會發生因內存或系統資源耗盡而導致數據庫出現 SEG-FAULT。
- 數學統計類型的查詢,例如count(),sum(), min(), collect()等。
這個例子對于 SQL 編程愛好者而言一點都不陌生——統計一家公司的員工的工資總和。
t(p).n(12).le({type:"works_for"}).n(c{type: "human"}).return(sum(c.salary))
在UQL中實現也是一句話的事情:
- 從公司頂點 12 出發
- 找到所有工作于(邊關系)本公司的員工,別名為 c
- 返回他們全部工資之和
在一張小表中,這個操作在 SQL 語境下同樣毫無壓力,但是在一張大表中(千萬或億萬行),或許這個 SQL 操作就會因為表掃描而變得緩慢了。而在嬴圖數據庫中因為采用相鄰哈希+近鄰存儲的存儲邏輯及并發邏輯優化,這種面向一步抵達的鄰居頂點的數學統計操作幾乎不會受到數據集大小的影響,進而可以讓任務執行時間基本恒定!
下面的這個例子中,則是統計該公司的員工都來自于哪幾個省:
t(p).n(12).e({type:"works_for"}).n(c{type:"employee"}).
return(collect(c.province))
上面的兩個例子是來說明通過UQL的方式同樣可以實現傳統關系型 SQL 查詢所能實現的功能。同樣,返回結果也可以以關系型數據庫查詢結果所常用的表單、表格的方式來呈現,例如下面的兩圖所示(Diagram-10-1 和 Diagram-10-2)。

Diagram-10-1:在嬴圖Manager中以表格的方式展示結果列表
上圖 Diagram-10-1中,khop() 操作返回的是從初始頂點出發經過 depth() 限定的深度搜索后返回的第K層的鄰居的集合,select() 的使用允許你選定需要具體返回的屬性。
下圖中展示的是類似的操作在嬴圖CLI中返回的結果示例。注意下圖中的時間有兩個維度,引擎時間和全部時間,其中引擎時間是內存圖計算引擎的運算耗費時間,而全部時間還包括一些持久化存儲層的數據轉換的時間。

Diagram-10-2:在嬴圖CLI中以表單的方式返回結果集
- 強大的基于模板的全文搜索。
如果一個數據庫系統中不能支持全文搜索,那么我們很難能稱其為完整的數據庫。在圖數據庫支持全文搜索并不是一個全新的事情,例如老牌的圖數據庫 Neo4j 中通過集成 Apache Lucene 的全文搜索框架,讓用戶可以通過 Cypher 語句來對頂點(及其屬性)進行全文本搜索。在嬴圖數據庫中我們并沒有采用開源的 Apache Lucene/Solr,其中一個很重要的原因是性能落差,在我們看來 Lucene/Solr 的架構的性能要指數級的低于嬴圖的核心計算引擎(另外一個次要的原因是這種開源的框架中依然存在著不可預知的一些問題,在生產環境中一旦暴露,修復起來非常困難,這個或許可被看做是開源的一個重大迷思)。
在UQL中完成面向頂點的全文搜索,只需要下面這句簡單的查詢語句:
find().nodes(~name:"Sequoia*").limit(100).select(name,intro)
這句UQL返回的是找到 100 個包含“紅杉”字樣的頂點,并返回它們的 name 和 intro 屬性。這個查詢非常類似于傳統數據庫中的面向某張表的列信息查詢。同樣的,也可以針對邊來進行查詢,例如下面:
find().edges(~name:"Love*").limit(200).select(*)
找到圖中所有的邊上的 name 屬性中存在“愛情“字樣的關系。
當然,如果我們的全文檢索只是停留在點、邊查詢,那么這就略顯單薄了。在嬴圖數據庫中,我們創造性的發明了基于模板匹配的全文本查詢——例如,模糊的搜索從“***紅杉***”出發到“***招銀***”的一張關聯關系網絡,網絡中的路徑搜索深度不超過 5 層,返回 20 條路徑所構成的子圖。注意:這個搜索時從模糊匹配頂點出發,到達模糊匹配的另外一套頂點!
t().n(~name: "Sequoia*").e()[:5].n(~name: CMB*").limit(20).select(name)
如果不用上面這句簡單得不能再簡單的UQL,你能想象如何用其它 SQL 或 NoSQL 語言來實現嗎?假設我們在一個工商數據集之上,在天眼查、企查查做類似的查詢,你要先找到名字中包含有紅杉或招銀字樣的公司,然后再分別的對每一家公司的投資關系進行梳理,你需要查清楚每家被投公司的合作、競爭、董監高等關系,然后再慢慢梳理出來是否能在 5 步之內關聯上名稱中包含紅杉字樣的一家公司和包含招銀字樣的另一家公司。這個操作絕對的是讓人瘋狂的,你可能需要花費數天的時間來完成,或者能夠通過寫代碼調用 API 的方式來“智能”化的實現。無論如何,你很難在下面兩件事情上擊敗UQL:
- 效率和時延(Efficiency and Latency):一言以蔽之實時性!
- 準確率和直觀度(Efficacy and Accuracy):直觀、易讀、易懂

Diagram-11:實時的基于模板查詢的全文搜索(嬴圖Manager)
在 Diagram-11 中,這個看起來簡單而又實際上非常復雜的查詢操作僅僅耗時 50ms!這種復雜查詢的效率性是前所未有的。如果讀者知道有任何其它數據庫系統可以在更短的時間內完成同樣的操作,歡迎聯系我們深度交流。
一門先進的(數據庫)查詢語言的優美感,不是通過它到底有多復雜,而是通過它有多簡潔來體現的。它應該具備這樣的一些通性:
- 易學、易懂(Easy to Learn,Easy to Understand)
- 高性能(Lightning Fast):當然,其實這個其實取決于底層的數據庫引擎!
- 系統的底層復雜性不應該暴露到語言接口層面(System Complexity Shielded-Off)
特別是上面的最后一點,如果讀者對于 SQL 或 Gremlin 或 Cypher 或 GraphSQL 當中復雜的嵌套邏輯心有余悸的話,你會更理解下面的這個比喻:當古希臘神話中的泰坦 Atlas 把整個世界(地球)抗在他的肩膀上的時候,世界公民們(數據庫用戶)并不需要去感知這個世界有多沉重(數據庫有多復雜)。
- 復雜的圖算法。
圖數據庫相比其他數據庫而言的一個明顯的優勢是集成化的算法功能支持。圖上有很多種算法,例如出入度、中心度、排序、傳播、連接度、社區識別、圖嵌入、圖神經元網絡等等。隨著商用場景的增多,相信會有更多的算法被移植到圖上或者被發明創造出來。
以魯汶社區識別算法為例,這個算法出現的時間僅僅 10 幾年,它得名于它的誕生地——比利時法語區的魯汶大學(Louvain University)。它最初被發明的目的是用來通過復雜的多次遞歸遍歷一張由社交關系屬性構成的大圖中的點、邊來找到所有的頂點(例如人、事、物)所構成的關聯關系社區,緊密關聯的頂點會處于同一社區,不同的頂點可能會處于不同的社區。在互聯網、金融科技領域,魯汶算法受到了相當的重視。下面這行UQL語句完成了魯汶算法的調用執行:
algo().louvain({phase1_loop:5, min_modularity_increase:0.01})
在圖數據庫中,調用一個算法與執行一個 API 調用是比較類似的,都需要提供一些必須的參數。上例中,用戶僅需提供最少兩個參數就可以執行魯汶。當然,可選的,用戶可以設定更為復雜的參數集來優化魯汶算法,因篇幅所限,本文在此并不做過多的展開描述,對此感興趣的讀者可以參考相關文檔。

Diagram-12:實時的魯汶社區識別算法及Web可視化(嬴圖Manager)
注:原生的魯汶社區識別算法的實現是串行的,也就是說它需要從全圖中的所有頂點出發,逐個頂點、逐條邊的去進行反復的運算,試想在一張大圖中(千萬頂點以上),這個計算的時間復雜度絕對的是要以 T+1 來衡量的。例如在 Python 的 NetworkX 庫中,對一個普通的(幾十萬-幾百萬頂點)圖數據集進行魯汶運算要耗時數個、數十個小時,但是在嬴圖數據庫上面這個計算的耗時通過高度的并發被劇烈的縮短到了毫秒-秒級!在這里,我們探討的不是 10 倍- 100 倍的性能超越,而是成千上萬倍的性能提升!如果讀者覺得我們給出的案例只是天方夜譚&癡人說夢,或許你應當重新審視一下你對于數據結構、算法以及它們的最優工程實現的理解了。
UQL中還支持很多功能強大的操作,上面的 5 個例子只是起到了一個拋磚引玉的效果,筆者希望它們能揭示UQL的簡介性,并喚起讀者去思考一個問題:你到底是愿意去絞盡腦汁的書寫成百上千行的 SQL 代碼,并借此殺死你我他的大量腦細胞來讀懂你的代碼呢?還是考慮用更簡潔、方便卻更加強大的圖語言呢?
關于數據庫查詢語言,我們認為:
- 數據庫查詢語言不應該只是數據科學家、分析員的專有工具,任何業務人員都可以(并應該)掌握一門查詢語言。
- 查詢語言應當便于使用,所有數據庫底層的架構、工程實現的復雜性應當對于上層的用戶而言是透明的!
- 最后,圖數據庫懷有巨大潛力,在未來的一段時間內會大幅的替代 SQL 的負載,有一些業界頂級的公司,例如微軟和亞馬遜已經預估未來 8-10 年間,會有 40-50% 的 SQL 負載會遷移到圖數據庫之上完成。讓我們拭目以待。
有些人認為,包括一些知名的投資機構和行業“專家”,關系型數據庫和 SQL 永遠也不會被取代。這種看法禁不起推敲。如果我們稍微回顧一下不是很久遠的歷史就會發現,關系型數據庫在 70-80 年代取代了導航型數據庫,它已經稱霸了行業 40 年了,但是越來越多的業務場景,SQL 類型數據庫或大數據系統捉襟見肘。它們的最大的弊端有 3 個:
- 性能與效率:低,特別是無法應對復雜查詢,太多 T+1 場景了。開發周期長。
- 靈活性:差,基于二維表的數據建模,天然的無法高效、靈活地描述業務場景。業務變動后,調整 SQL 存儲進程代碼過于復雜、耗時、成本高的問題,無解。
- 可解釋性:差,越復雜的場景 SQL 代碼越復雜,逐步演變為拖累業務拓展。
而以上這 3 點,恰恰都是圖數據庫的優勢。