一 AgensGraph簡(jiǎn)介
AgensGraph 是一個(gè)基于 PostgreSQL 的新一代多模型圖數(shù)據(jù)庫(kù)。它提供圖形分析環(huán)境,用戶可以同時(shí)編寫(xiě)、編輯和執(zhí)行 SQL 和 Cypher 查詢。AgensGraph 帶有 PostgreSQL 兼容性和 PostgreSQL擴(kuò)展,能夠幫助PostgreSQL用戶擺脫數(shù)據(jù)遷移的痛苦,輕松開(kāi)發(fā)提供高級(jí)數(shù)據(jù)分析的服務(wù)。
1.1 AgensGraph特點(diǎn)
- 1 萬(wàn)物互聯(lián),無(wú)論是蛋白網(wǎng)絡(luò)還是電網(wǎng),圖數(shù)據(jù)庫(kù)是必然需求,標(biāo)準(zhǔn)Cypher圖查詢。
- 2 運(yùn)維配置同PostgreSQL幾乎一樣,熟悉PG的DBA和開(kāi)發(fā)人員上手容易,開(kāi)發(fā)效率高。
- 3 支持sql和Cypher混合查詢。兩種查詢可以實(shí)現(xiàn)join操作,從而實(shí)現(xiàn)圖查詢結(jié)果匹配對(duì)應(yīng)業(yè)務(wù),整體結(jié)果輸出,減少后端開(kāi)發(fā)工作。
- 4 支持csv導(dǎo)入導(dǎo)出,方便rdb數(shù)據(jù)遷移。
- 5 可以使用PG庫(kù)豐富的其他特性,比如 圖+PotGIS,或者數(shù)據(jù)庫(kù)自己的事務(wù)隔離等等,分布式的話,可以參考pg的分布式方案(個(gè)人認(rèn)為是不改源碼的那種,比如fdw或者citus方案,其他pgxc/xl和gp不適合,因?yàn)檫@個(gè)agens不是插件,是單獨(dú)的改的pg的數(shù)據(jù)庫(kù),插件可以用)。
1.2 AgensGraph vs NoSQL
AgensGraph即支持圖模型,還支持關(guān)系模型,文檔模型,kv模型。

二 AgnesGraph安裝
安裝和PostgreSQL幾乎一模一樣,詳情如下:
2.1 安裝依賴
yum install gcc glibc glib-common readline readline-devel zlib zlib-devel flex bison
2.2 創(chuàng)建用戶
[root@zz ~]# useradd agens
[root@zz ~]# chown -R agens.agens /home/agens
2.3 源碼安裝
#下載release
[root@zz ~]#wget https://github.com/bitnine-oss/agensgraph/archive/v2.1.1.tar.gz
#解壓
[root@zz ~]# tar -zxvf v2.1.1.tar.gz
#進(jìn)入目錄安裝
[root@zz ~]# cd agensgraph-2.1.1
[root@zz agensgraph-2.1.1]# ./configure --prefix=/home/agens
[root@zz agensgraph-2.1.1]# make
[root@zz agensgraph-2.1.1]# make install
#安裝contrib的pg擴(kuò)展工具
[root@zz agensgraph-2.1.1]# cd contrib
[root@zz contrib]# make
[root@zz contrib]# make install
2.4 數(shù)據(jù)庫(kù)初始化
2.4.1 設(shè)置環(huán)境變量
[root@zz contrib]# su - agens
[agens@zz ~]$ vi .bashrc
#編輯內(nèi)如如下:
AGHOME=/home/agens
export AGHOME
AGDATA=$AGHOME/data
export AGDATA
PATH=$PATH:$HOME/.local/bin:$HOME/bin:$AGHOME/bin
export PATH
#保存啟用環(huán)境變量
[agens@zz ~]$ source .bashrc
#初始化數(shù)據(jù)庫(kù)
[agens@zz ~]$ initdb -D $AGDATA
#啟動(dòng)數(shù)據(jù)庫(kù)
[agens@zz ~]$ ag_ctl -D $AGDATA
#psql登錄數(shù)據(jù)庫(kù),進(jìn)入我們熟悉的psql環(huán)境
[agens@zz ~]$ psql -d postgres
psql (10.4)
Type "help" for help.
postgres=#
結(jié)論:和postgresql安裝和操作一模一樣,其他的如登錄授權(quán)的pg_hba.conf和服務(wù)設(shè)置文件postgresql.conf設(shè)置和pg也是一樣的。
三 AgensGraph概念
3.1 AgensGraph數(shù)據(jù)庫(kù)架構(gòu)
AgensGraph是一個(gè)多模型數(shù)據(jù)庫(kù),既支持帶屬性的圖模型,也支持關(guān)系模型。

Agens是基于PostgreSQL數(shù)據(jù)庫(kù)的,通過(guò)schema,支持原生的關(guān)系模型,也就是普通的關(guān)系表等;通過(guò)graph,支持圖模型的vertice(頂點(diǎn))和edge(邊)。一句話,在Agens你可以操作關(guān)系表,也可以操作圖。
3.2 圖模型概念

頂點(diǎn)(Vertices):一個(gè)實(shí)體一個(gè)頂點(diǎn)。一個(gè)實(shí)體可以有多個(gè)屬性。
邊(Edges):兩個(gè)實(shí)體之間的連接線。當(dāng)試圖刪除頂點(diǎn)時(shí),要先刪除連接該頂點(diǎn)的邊才行(AgensGraph中不允許存在斷邊,也就是邊一定會(huì)有連接的兩個(gè)頂點(diǎn))。
屬性:實(shí)體和邊都可以有多個(gè)屬性。形象舉個(gè)例子,一個(gè)實(shí)體對(duì)應(yīng)關(guān)系表中一行記錄,一個(gè)實(shí)體的屬性代表關(guān)系表中這行記錄的所有字段和值構(gòu)成的鍵值對(duì)。
標(biāo)簽:AgensGraph中有VLABEL ,ELABEL 兩個(gè)對(duì)象,分別定義頂點(diǎn),邊的分組。形象舉個(gè)例子,標(biāo)簽類似與關(guān)系表的表名稱,那么一個(gè)標(biāo)簽分組下的實(shí)體和邊,其屬性的鍵名稱和數(shù)量一致。(ps:理論上是可以不一致,屬性是nosql的json形式,但是,實(shí)際中的圖大部分是關(guān)系表導(dǎo)出,所以實(shí)際中幾乎一致)。
四 AgensGraph練習(xí)
方便后面描述,新建一個(gè)測(cè)試數(shù)據(jù)庫(kù)作圖數(shù)據(jù)庫(kù)的測(cè)試庫(kù)。
[agens@zz ~]$ psql -d postgres
psql (10.4)
Type "help" for help.
#新建測(cè)試庫(kù)
postgres=# create database agens_test;
#切換測(cè)試庫(kù)
postgres=# \c agens_test;
4.1 圖管理
4.1.1 創(chuàng)建
agens_test=# create graph dlxx;
4.1.2 查詢當(dāng)前應(yīng)用圖
agens_test=# SHOW graph_path;
graph_path
------------
dlxx
(1 row)
4.1.3 設(shè)置當(dāng)前應(yīng)用圖
假設(shè)當(dāng)前應(yīng)用圖為空,可以先設(shè)置一個(gè)應(yīng)用圖,如下:
agens_test=# SHOW graph_path;
graph_path
------------
(1 row)
agens_test=# set graph_path='dlxx';
SET
agens_test=# SHOW graph_path;
graph_path
------------
dlxx
(1 row)
4.1.4 刪除圖
cacsade級(jí)聯(lián)刪除,會(huì)將該圖下的對(duì)象都刪除:
agens_test=# drop graph dlxx cascade;
4.2 標(biāo)簽(Lables)管理
4.2.1 標(biāo)簽創(chuàng)建
#創(chuàng)建頂點(diǎn)標(biāo)簽
agens_test=# CREATE VLABEL person;
CREATE VLABEL
#創(chuàng)建頂點(diǎn)標(biāo)簽,標(biāo)簽friend繼承標(biāo)簽person。
agens_test=# CREATE VLABEL friend inherits (person);
CREATE VLABEL
#創(chuàng)建邊標(biāo)簽
agens_test=# CREATE ELABEL knows;
CREATE ELABEL
agens_test=# CREATE ELABEL live_together;
CREATE ELABEL
#創(chuàng)建邊標(biāo)簽,標(biāo)簽room_mate繼承標(biāo)簽knows和live_together
agens_test=# CREATE ELABEL room_mate inherits (knows, live_together);
CREATE ELABEL
4.2.2 標(biāo)簽刪除
agens_test=# drop ELABEL knows;
ERROR: cannot drop elabel knows because other objects depend on it
DETAIL: elabel room_mate depends on elabel knows
HINT: Use DROP ... CASCADE to drop the dependent objects too.
# 因?yàn)镋LABEL room_mate繼承knows,所以刪除不了??梢酝ㄟ^(guò)兩個(gè)方式刪除
# 方式一
agens_test=# drop ELABEL room_mate;
agens_test=# drop ELABEL knows;
#方式二
agens_test=# drop ELABEL knows cascade;
這些操作其實(shí)都是pg的,一模一樣。
4.3 索引和約束
待補(bǔ)充
4.4 圖查詢
4.4.1 創(chuàng)建圖的連接性
頂點(diǎn)數(shù)據(jù)格式:(variable:label {property: value, ...})
邊數(shù)據(jù)格式:-[variable:label {property: value, ...}]-
最左邊的附加<或最右邊的>用于表示邊的方向
CREATE VLABEL person;
CREATE ELABEL knows;
#name=tom的頂點(diǎn),通過(guò)邊(knows),連接最右邊的name=summer的頂點(diǎn)
CREATE (:person {name: 'Tom'})-[:knows {fromdate:'2011-11-24'}]->(:person {name: 'Summer'});
CREATE (:person {name: 'Pat'})-[:knows {fromdate:'2013-12-25'}]->(:person {name: 'Nikki'});
CREATE (:person {name: 'Olive'})-[:knows {fromdate:'2015-01-26'}]->(:person {name: 'Todd'});
#使用MATCH命令,從Person的頂點(diǎn)標(biāo)簽 選擇name屬性為tom和pat的兩個(gè)頂點(diǎn)
#這兩個(gè)頂點(diǎn)分別設(shè)置代指p和k,創(chuàng)建p-edge-k的圖連接關(guān)系
MATCH (p:Person {name: 'Tom'}),(k:Person{name: 'Pat'})
CREATE (p)-[:KNOWS {fromdate:'2017-02-27'} ]->(k);
圖形如下:

示例1:查詢與屬性name=‘Tom’的頂點(diǎn)有直接連接關(guān)系的其他標(biāo)簽為person的頂點(diǎn):
MATCH (n:person {name: 'Tom'})-[:knows]->(m:person) RETURN n.name AS n, m.name AS m;
n | m
-------+----------
"Tom" | "Summer"
"Tom" | "Pat"
(2 rows)
示例2:查詢與屬性name=‘Tom’的頂點(diǎn)有兩層連接關(guān)系的其他標(biāo)簽為person的頂點(diǎn):
MATCH (p:person {name: 'Tom'})-[:knows]->()-[:knows]->(f:person) RETURN f.name;
name
---------
"Nikki"
(1 row)
示例3:使用union將圖查詢結(jié)果合并輸出:
MATCH (p:person {name: 'Tom'})-[:knows]->(f:person) RETURN f.name
UNION ALL
MATCH (p:person {name: 'Tom'})-[:knows]->()-[:knows]->(f:person) RETURN f.name;
name
----------
"Summer"
"Pat"
"Nikki"
(3 rows)
以上語(yǔ)句可以使用以下等效,注意[r:knows*1..2]寫(xiě)法,1..2代表1級(jí)到2級(jí):
MATCH (p:person {name: 'Tom'})-[r:knows*1..2]->(f:person)
RETURN f.name, r[1].fromdate;
name | fromdate
----------+--------------
"Summer" |
"Pat" |
"Nikki" | "2013-12-25"
(3 rows)
在圖數(shù)據(jù)庫(kù)中,典型的查詢是查找位于可變連接長(zhǎng)度的邊-頂點(diǎn)路徑之后的頂點(diǎn)。 邊中使用的* 1..2表示這樣的變長(zhǎng)邊。 其中1是邊的最小長(zhǎng)度,2是最大長(zhǎng)度。
不指定值,默認(rèn)值應(yīng)該是1,就是就一層連接(直接連接關(guān)系,有一個(gè)邊即可達(dá)):
MATCH (p:person {name: 'Tom'})-[r:knows]->(f:person) RETURN f.name;
name
----------
"Summer"
"Pat"
(2 rows)
指定*,就是有多個(gè)邊可達(dá),都屬于查詢要求:
MATCH (p:person {name: 'Tom'})-[r:knows*]->(f:person) RETURN f.name;
name
----------
"Summer"
"Pat"
"Nikki"
(3 rows)
4.5 圖處理(增刪改)
4.5.1 create
CREATE (:person {name: 'Tom'})-[:knows {fromdate:'2011-11-24'}]->(:person {name: 'Summer'});
4.5.2 set
MATCH (:person {name: 'Tom'})-[r:knows]->(:person {name: 'Summer'})
SET r.since = '2009-01-08';
4.5.3 delete
MATCH (n:person {name: 'Pat'}) DETACH DELETE (n);
DETACH DELETE將相關(guān)的頂點(diǎn)和邊一次性全部刪除。類似sql的 cascade,級(jí)聯(lián)刪除,因?yàn)閍gens不允許斷邊存在,刪除了頂點(diǎn),這個(gè)頂點(diǎn)相關(guān)的邊都要?jiǎng)h了,使用DETACH DELETE更常用。
4.5.4 merge
這個(gè)和sql中的insert into on confict do很像,沒(méi)有就插入,有就更新,是個(gè)命令合并語(yǔ)法。
merge的意思是,如果圖中有查詢條件的數(shù)據(jù),等同于match,就是查找。如果圖中沒(méi)有查詢條件的數(shù)據(jù),等同于create,就是創(chuàng)建。
merge就是有就match,沒(méi)有就create的復(fù)合語(yǔ)法。
CREATE VLABEL customer;
CREATE VLABEL
CREATE VLABEL city;
CREATE VLABEL
CREATE (:customer {name:'Tom', city:'santa clara'}),
(:customer {name:'Summer ', city:'san jose'}),
(:customer {name:'Pat', city:'santa clara'}),
(:customer {name:'Nikki', city:'san jose'}),
(:customer {name:'Olive', city:'san francisco'});
GRAPH WRITE (INSERT VERTEX 5, INSERT EDGE 0)
#檢索所有的custome,將city屬性通過(guò)merge語(yǔ)法到city頂點(diǎn)標(biāo)簽。
MATCH (a:customer) MERGE (c:city {name:a.city});
GRAPH WRITE (INSERT VERTEX 3, INSERT EDGE 0)
#原來(lái)city標(biāo)簽是未賦值的,merge之后,屬性值發(fā)生變化。
MATCH (c:city) RETURN properties(c);
properties
---------------------------
{"name": "santa clara"}
{"name": "san jose"}
{"name": "san francisco"}
(3 rows)
MERGE可以通過(guò) ON MATCH SET和 ON Create SET修改對(duì)應(yīng)圖中頂點(diǎn)或者邊的屬性。
CREATE (:customer {name:'Todd', city:'palo alto'});
MATCH (a:customer)
MERGE (c:city {name:a.city})
ON MATCH SET c.matched = 'true'
ON CREATE SET c.created = 'true';
#匹配到的是matched=true,未匹配的會(huì)create,設(shè)置created=true
MATCH (c:city) RETURN properties(c);
properties
----------------------------------------------
{"name": "santa clara", "matched": "true"}
{"name": "san jose", "matched": "true"}
{"name": "san francisco", "matched": "true"}
{"name": "palo alto", "created": "true"}
(4 rows)
4.5.5 事務(wù)隔離
BEGIN;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
MATCH (a:customer)
MERGE (c:city {name:a.city});
COMMIT;
4.5.6 最短路徑
新建一個(gè)連接,tom指向olive:
MATCH (p:person {name:'Tom'}), (f:person {name:'Olive'}) CREATE (p)-[:knows]->(f);
GRAPH WRITE (INSERT VERTEX 0, INSERT EDGE 1)
新建后的圖更新如下:

shortestpath方法用于查詢最短路徑,需要制定起點(diǎn),終點(diǎn)的頂點(diǎn),可以通過(guò)-[:knows*1..5]-這只edge的查找深度,比如1-5層連接關(guān)系:
MATCH (p1:person {name: 'Tom'}), (p2:person {name: 'Todd'}),
path=shortestpath((p1)-[:knows*1..5]->(p2)) RETURN path;
path
-----------------------------------------------------------------------------------------------------------------------------------------------------------
[person[3.1]{"name": "Tom"},knows[5.5][3.1,3.5]{},person[3.5]{"name": "Olive"},knows[5.3][3.5,3.6]{"fromdate": "2015-01-26"},person[3.6]{"name": "Todd"}]
(1 row)
查詢結(jié)果是:tom到olive到todd,上圖示意符合一致。
4.5.7 sql和圖查詢的混合查詢
構(gòu)建下測(cè)試圖數(shù)據(jù)與關(guān)系表數(shù)據(jù):
CREATE GRAPH bitnine;
CREATE VLABEL dev;
CREATE (:dev {name: 'someone', year: 2015});
CREATE (:dev {name: 'somebody', year: 2016});
CREATE TABLE history (year, event)
AS VALUES (1996, 'PostgreSQL'), (2016, 'AgensGraph'),(2019, 'sssss');
執(zhí)行查詢,查詢圖中年份小于history的年份。圖在agens其實(shí)都是jsonb的nosql格式,可以通過(guò)->>等pg中json,jsonb格式根據(jù)鍵名稱獲取值等函數(shù),詳細(xì)查看pg的jsonb function文檔:
cypher in sql:
SELECT history.*,n->>'name' as name
FROM history, (MATCH (n:dev) RETURN n) as dev
WHERE history.year > (n->>'year')::int;
year | event | name
------+------------+----------
2016 | AgensGraph | someone
2019 | sssss | someone
2019 | sssss | somebody
(3 rows)
sql in cypher:
MATCH (n:dev) WHERE n.year < (SELECT year FROM history WHERE
event = 'AgensGraph') RETURN properties(n) AS n;
n
-----------------------------------
{"name": "someone", "year": 2015}
(1 row)