图数据库简介

小天
• 阅读 1508

概况

图数据库(Graph database,GDB)是一个使用图结构进行语义查询的数据库,它使用节点、边和属性来表示和存储数据。该系统的关键概念是图,它直接将存储中的数据项,与数据节点和节点间表示关系的边的集合相关联。

图形数据库应用场景非常广泛,可以说只要传统关系型数据库能够应用的场景,图数据库都能够胜任。在业界,最著名的应用案例为Meta公司的社交图谱 (Social Graph),几乎Meta所有的产品都是基于社交图谱构建的。著名的社交六度分隔理论,便是基于社交图谱研究而来的。 图数据库简介

在图形数据库,节点和边是基本概念

  • 点 (Node):也称“节点”,用于描述数据关系中的主体。点可以有属性(attributes)。比如,在社交场景中,人就是点,名字,性别等就为属性。
  • 边(Vertex):用于描述数据之间的关系,可以有向也可以无向(也可理解为双向)。一般来说,边应至少包括两个点的标识符。比如,认识关系就是边(单项),夫妻关系也可以是边(双向)。

图数据库简介

除了点跟边,一般业界的图数据库还包括:

  • 触发器(Trigger):用于触发特定事件。比如某人年龄到达18,可以触发事件“成年”。
  • 观察器(Observer):用于监听事件,从而进行实时处理。比如,当“成年”时间触发以后,可以进行一些列处理,比如推送“恭喜成年”消息。
  • 验证器(Validator):主要用于保持数据一致性。一致性既可以是点的,也可以是边的。比如人的年龄不能为负数,在多数国家,一个人只能有一条夫妻关系的边,等等。

当然,每个数据库会有一些出入或者特定的机制,这里就不一一列举了。

检索

图数据库通常会提供类似于图遍历(Graph Traversal)的检索机制,并附带语言级别(QL)的检索支持。可惜的是,到目前位置还没有类似于RDBSM那样标准的检索语言(SQL)。有些图数据库会更进一步提供接口API进行数据检索。此外,图数据库非常容易整合ORM(Object-Relational Mapping),进而更好地解耦数据存储和数据模型。

如前文所说,不同数据库检索语言和API会有所差异,但原理基本一致。在此,我们会使用伪代码展示。例如,在社交例子中,我们需要查询用某一个用户的所有认识的人,检索伪代码为

Select(User)
->Where(userID = $USER_ID)
->QueryViaEdge_Know // 访问"认识"边
->Scan() // 返回所有"认识"的用户

如果我们需要进一步访问某一用户在中国认识的其他用户,则

Select(User)
->Where(userID = $USER_ID)
->QueryViaEdge_Know // 访问"认识"边
->Where(Country = "China") // Country 是用户点的一个属性
-> Scan()

再进一步,如果我们想知道某用户在中国认识的用户里所关注的活动。在此,假设活动为点,关注活动为边,则

Select(User)
->Where(userID = $USER_ID)
->QueryViaEdge_Know // 访问"认识"边
->Where(Country = "China")
->QueryViaEdge_FollowEvent // 进一步访问"关注活动"边
->Scan()

有的时候,我们还需要一些更加复杂的检索,比如子检索(Sub-Query)支持。还是回到社交的例子,如果我们想要找到某用户在中国认识并且订阅了该用户的其他用户。此处假设“认识”和“订阅”都是连接不同用户的边。则

Select(User)
->Where(userID = $USER_ID)
->QueryViaEdge_Know // 访问"认识"边
->Where(Country = "China")
->Where(
    SubQuery: //所有当前所达点会作为参数传入子检索
      TargetNode
      -> QueryViaEdge_Subscribe
      -> Where(SubscribeToUserID = $USER_ID)
  ) //在子检索中,会只留下关注了USER_ID的结果并返回主检索
->Scan()

与关系型数据库对比

下面我们用一个实例来展示图数据库相比传统的关系型数据库(RDB)的异同。我们考虑一个论坛,有如下基本功能

  • 发表博客
  • 点赞博客
  • 订阅用户

如果是传统关系型数据库(RDB),则一般需要如下表,为

  • 用户
  • 博客 (作者为外键,指向用户表中的UserID)
  • 点赞连接表(用户 <-> 博客)
  • 订阅连接接表(用户 <-> 用户)

如果是图数据库(GDB),则为

  • 两种节点(用户,博客)
  • 三种关系(赞,拥有,订阅)

图数据库简介

我们发现,图数据库更好地描述了数据关系,并且没有复杂的连接表以及外键设置。下面我们考虑改变需求,增加笔记功能:笔记需有一人拥有,但可以多人协作。笔记可以直接发布为博客。如果是RDB,则会更改为如下表格

  • 用户
  • 博客 (作者为外键,指向用户表中的UserID)
  • 笔记 (拥有者为外键,指向用户表中的UserID)
  • 协作连接表 (笔记 <-> 用户)
  • 点赞连接表(用户 <-> 博客)
  • 订阅连接接表(用户 <-> 用户)

我们会看到,对于传统RDB,需要增加更多的表格(对象表,连接表)从而让检索变得相对复杂。比如,我们需要检索某一用户拥有的笔记中的协作者所订阅的用户是否含有该用户,那么至少需要JOIN 笔记表,协作连接表,订阅表。

// Sub queries
WITH ( 
SELECT note_share.shared_user_id FROM note JOIN note_share ON note.id = note_share.id WHERE UserID = $USER_ID
) AS shared_user_ids,
(SELECT * FROM subscription WHERE subscribe_to_user_id = $USER_ID) AS
subscriber_candidates 

// Main query
SELECT COUNT(*) FROM subscriber_candidates WHERE subscriber_user_id IN shared_user_ids

如果是GDB,那么

图数据库简介

而检索可以清晰地表达为:

Select(note)
->Where(OwnerUserID = $USER_ID)  // 检索该用户拥有的笔记
->QueryViaEdge_SharedWith   // 检索笔记分享的用户
->QueryViaEdge_Subscribe    // 检索笔记分享用户所订阅的用户
->Where(SubscribeToUserID = $USER_ID) 
->Exist()

我们可以清楚地看到,相比于RBD需要JOIN诸多表格,GDB更够更清晰地表达检索语义。

接下来我们继续加码,增加打赏功能:即用户需要连接支付支付账户,并且对喜爱的博客作者打赏,每次打赏的交易信息需要存底保留。如果是RDB,则需要

  • 用户
  • 账户
  • 用户账户连接表 (用户 <-> 账户,一个用户可以连接多个账户,打赏时可以选择从哪个进行支付)
  • 交易记录
  • 博客 (作者为外键,指向用户表中的UserID)
  • 笔记 (拥有者为外键,指向用户表中的UserID)
  • 协作连接表 (笔记 <-> 用户)
  • 点赞连接表(用户 <-> 博客)
  • 订阅连接接表(用户 <-> 用户)

如果我们想要知道某一用户拥有的博客中的点赞者中,除去博客原来笔记协作者外,总共被打赏的金额是多少?我想对于RDB来说,这个SQL已经相当复杂了,绝大多数情况需要进行多次检索,外加大部分内存操作才能完成。

如果我们用GDB,我们需要

  • 增加账户点,有一个边连接用户
  • 增加一个打赏边,连接用户和博客,该打赏边除了有所连接的连个点ID的属性,还有一个交易记录ID,指向一个交易记录 图数据库简介

那么检索依然可以表达为

$transactionIDs = 
Select(blog)
->Where(OwnerUserID = $USER_ID)  // 检索该用户拥有的博客
->QueryEdge_Reward    // 此处我们检索边本身,而不是连接点
->Where(RewardUserID NOT IN
    SubQuery: 
      TargetEdge
      ->Select(note)
      ->Where (OwnerUserID = $USER_ID)
      ->QueryEdge_Share
      ->ToIDs()
  ) //在子检索中,根据$USER_ID所拥有的笔记,找到分享对象的UserIDs,并把由这些用户的打赏排除在外
->TransactionIDs();

$amount = 
Select(transaction)
->Where(ID in $transactionIDs)
->Sum(value);

总结

图数据库是一种新型的非关系型数据库。相比于传统的以表为基础的关系型数据库,能够更好地描述数据关系,覆盖更广泛的应用场景,提供更强大的查询语义,以及更好地解耦数据存储和数据模型,从而大大地提高了开发效率。

点赞
收藏
评论区
推荐文章
Easter79 Easter79
3年前
sql注入
反引号是个比较特别的字符,下面记录下怎么利用0x00SQL注入反引号可利用在分隔符及注释作用,不过使用范围只于表名、数据库名、字段名、起别名这些场景,下面具体说下1)表名payload:select\from\users\whereuser\_id1limit0,1;!(https://o
Stella981 Stella981
3年前
Neo4j 第一篇:在Windows环境中安装Neo4j
<divid"cnblogs\_post\_body"class"blogpostbody"<p图形数据库(GraphDatabase)是NoSQL数据库家族中特殊的存在,用于存储丰富的关系数据,Neo4j是目前最流行的图形数据库,支持完整的事务,在属性图中,图是由顶点(Vertex),边(Edge)和属性(Property)组成的,顶点和
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
3年前
Neo4j图数据库从入门到精通(转)
addbyzhj:转载时,目录没整理好,还会跳转到原文其实RDB也可以存储多对多的关系,使用的是中间表,GDB使用的是边,RDB中的实体存储在数据表,而GDB存储在节点。两者使用的底层技术不同,但解决相同的问题。对于使用者来讲,GDB和RDB区别不大,图数据库中的概念,语法在关系数据库中基本都能找到,所以上手很快。Neo4j的特点
Wesley13 Wesley13
3年前
mysql中时间比较的实现
MySql中时间比较的实现unix\_timestamp()unix\_timestamp函数可以接受一个参数,也可以不使用参数。它的返回值是一个无符号的整数。不使用参数,它返回自1970年1月1日0时0分0秒到现在所经过的秒数,如果使用参数,参数的类型为时间类型或者时间类型的字符串表示,则是从1970010100:00:0
Stella981 Stella981
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Easter79 Easter79
3年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Stella981 Stella981
3年前
ELK学习笔记之ElasticSearch的索引详解
0x00ElasticSearch的索引和MySQL的索引方式对比Elasticsearch是通过Lucene的倒排索引技术实现比关系型数据库更快的过滤。特别是它对多条件的过滤支持非常好,比如年龄在18和30之间,性别为女性这样的组合查询。倒排索引很多地方都有介绍,但是其比关系型
菜园前端 菜园前端
1年前
什么是图?
原文链接:什么是图?图是网络结构的抽象模型,是一组由边连接的节点。图可以表示任何二元关系,比如道路、航班等。在JavaScript中没有图,但是可以通过Object和Array来构建图。常用操作深度优先遍历广度优先遍历图的表示法邻接矩阵邻接表关联矩阵...