一. 前言
来学习下图数据以及图数据库
二. 图数据库的简单原理
2.1 图数据
我认为图数据结构就是点线面的关系,图大致分为以下概念 :
- 节点 : 图中的基本元素,可以用来表示现实世界中的一个**实体 **
- 边 : 节点之间连接的线,用来描述实体之间的关系 边可以有方向,有起始节点到终止节点边可以包含属性
- 属性 :节点和边都可以包含属性,属性存储有关实体的信息
- 权重 :权重指边上的一个值,用来标识成本,强度等等概念 (最短路径,最低消耗等)
- 路径 :一个节点到另外一个节点的路程表示路径,一个路径可以包含多个节点多个边
分类
- 按照图的方向性可以分为 :有向图,无向图
- 按照边是否带权重可以分为 :有权图,无权图
- 按照边的多少可以分为 : 稠密图,稀疏图
- 按照特定类型包括 : 邻接矩阵、邻接表、关联矩阵
2.2 图数据案例
- Neo4j: Neo4j是最受欢迎的图数据库之一,采用图结构来存储数据,并提供高效的图查询和遍历功能。Neo4j支持Cypher查询语言,适用于各种图数据分析任务
- OrientDB : 一个多模型数据库,支持图数据库、文档数据库和对象数据库
- Dgraph : 一个分布式图数据库,支持属性图和图数据库功能
好了,图的基本概念是清楚了,用法我们基于 Neo4j 来看
三. 图数据库 Neo4j
2.1 创建图数据库 Neo4j
可惜找了几个云服务商都没能薅羊毛,没看到可以免费领取的图数据库,所以只能自己安装下 Neo4j 了,简单点还是基于 Docker :
准备 docker-compose.yml
version: '3'
services:
neo4j:
image: neo4j:latest
container_name: neo4j
ports:
- "7474:7474"
- "7687:7687"
environment:
NEO4J_AUTH: neo4j/test123456
安装运行
docker-compose up -d
> 访问地址
http://localhost:7474/
2.2 弄清楚 Neo4j 里面的一些知识点
- 可以通过配置创建多个Neo4j 实例,但是这种创建属于物理层面,需要修改底层配置
- 不同于关系型数据库和NoSQL , 一个 Neo4j 实例里面只有一个主图和多个子图(Enterprise版本才有子图)
- 标签和属性更像一种分类,而不是单纯的看成节点的字段
支持的索引
- 节点索引 :基于节点的属性创建索引
- 关系索引 :用于关系的查找操作,基于关系的属性创建索引
- 全文索引 :在节点的文本属性上创建,执行全文搜索查询
- 空间索引 :用于地理空间数据 ,支持范围搜索和最近搜索
- 时间/属性/组合等自定义索引 :其他的索引类型
CREATE INDEX ON :Person(name)
2.3 Neo4j 使用流程
使用层面上我们首先创建一个人物画像/社交网络,用于后续的分析 :
S1 : 创建节点
- 语法 : CREATE (node:Label {property: value})
- node 是节点变量名,可以通过 return 返回这个变量,用于后续使用,本身无含义
- property 表示节点的属性,键值对关系,可以有多个
- label :节点标签,用来标识节点的类型
CREATE (n1:User {name:'会员A',level:'1'}) RETURN n1;
CREATE (n2:User {name:'会员B',level:'2'}) RETURN n2;
CREATE (n3:User {name:'会员C',level:'3'}) RETURN n3;
CREATE (n4:User {name:'会员D',level:'4'}) RETURN n4;
CREATE (n5:User {name:'会员E',level:'5'}) RETURN n5;
// 创建节点的时候创建关系
CREATE (a:User {name:'会员F'})-[r:FRIENDS]->(b:User {name:'会员D'})
S2 : 查询节点
- 查询所有节点 :MATCH (n:User {}) RETURN n
- 查询特定属性 :MATCH (n:User {name:'会员A'}) RETURN n
查询到的结果
S3 : 为节点创建关系
MATCH (startNode:Label1), (endNode:Label2)
CREATE (startNode)-[:RELATIONSHIP_TYPE]->(endNode)
- MATCH 子句用于匹配起始节点(startNode)和结束节点(endNode),通常使用标签和属性来过滤节点
- CREATE 子句用于创建关系,表示连接起始节点和结束节点
- [:RELATIONSHIP_TYPE] 是关系的类型,可以替换为想要创建的关系类型
- 箭头表示关系的方向,从起始节点指向结束节点
// 创建 User 节点间的关系 (关系为朋友)
MATCH (a:User {name:'会员A'}),
(b:User {name:'会员B'})
MERGE (a)-[:FRIENDS]->(b)
// ↑↑↑↑ 解释
- 这里的 a 和 b 都是节点别名,用于创建关系时使用
- User 标识的是节点类型,查询这一类节点
- name 才是匹配节点的关联关系,这里是 name ,也可以是 id 等更加具体的属性
S4 : 关系查询
PS : 这里的 sn ,en 都是别名,表示是 startNode 、 endNode ,篇幅有限
- 查询特定的关系的关系 :MATCH ()-[relation:FRIENDS]->() RETURN relation
- 查询有特定关系的节点 :MATCH (n)-[:FRIENDS]-() RETURN n
- 查询特定节点和关系 :MATCH (n:User)-[:FRIENDS]-(friend:User) RETURN n, friend
// 复杂查询
- 复杂语句 :MATCH (sn)-[relation]-(en) WHERE sn.name = '会员A' AND en.name = '会员B' RETURN relation
- 特定关系 :MATCH (sn)-[rl:FRIENDS]->(en) WHERE sn.name = '会员A' RETURN rl, en
- 查询属性 :MATCH ()-[relation:FRIENDS]->() RETURN relation.propertyName
- 查询数量 :MATCH (sn)-[rl]->(en) WHERE sn.name = '会员A' RETURN COUNT(rl)
- 查询路径 :MATCH path = (sn)-[rl*]->(en) WHERE sn.name = '会员A' AND en.name = '会员C' RETURN path
以上基础就完成了,后续就进行相关的业务扩展了
S5 : 创建更多类型的节点
CREATE (n1:address {name:'武汉'}) RETURN n1;
CREATE (n2:address {name:'上海'}) RETURN n2;
CREATE (n3:address {name:'北京'}) RETURN n3;
CREATE (n4:address {name:'深圳'}) RETURN n4;
CREATE (n5:address {name:'杭州'}) RETURN n5;
S6 : 创建复杂的关系图
// 地址关联
MATCH (a:User {name:'会员A'}), (b:address {name:'武汉'}) MERGE (a)-[:ADDRESS]->(b);
MATCH (a:User {name:'会员B'}), (b:address {name:'武汉'}) MERGE (a)-[:ADDRESS]->(b);
MATCH (a:User {name:'会员C'}), (b:address {name:'武汉'}) MERGE (a)-[:ADDRESS]->(b);
MATCH (a:User {name:'会员D'}), (b:address {name:'上海'}) MERGE (a)-[:ADDRESS]->(b);
MATCH (a:User {name:'会员E'}), (b:address {name:'北京'}) MERGE (a)-[:ADDRESS]->(b);
MATCH (a:User {name:'会员A'}), (b:address {name:'深圳'}) MERGE (a)-[:FRIENDS]->(b);
MATCH (a:User {name:'会员A'}), (b:address {name:'杭州'}) MERGE (a)-[:FRIENDS]->(b);
// 朋友关联 :
MATCH (a:User {name:'会员B'}), (b:User {name:'会员E'}) MERGE (a)-[:FRIENDS]->(b);
S7 : 最终效果
// 查询所有节点和节点间的关系 :
MATCH (n) RETURN n
// 查询所有有关系的节点
MATCH (a)--() RETURN a
// 查询所有对外有关系的节点,以及关系类型
MATCH (a)-[r]->() RETURN a.name, type(r)
S8 : 基于图的业务价值
// 查询与武汉节点关联的节点
MATCH (a:address {name: '武汉'})-[r]-(b) RETURN b
MATCH (a:address {name: '武汉'})-[r]-(b) RETURN a,b
- PS : 这里 return 可以返回多个,展示的效果是不同的
// 查询朋友的朋友
MATCH (a:User {name:'会员A'})-[r1:FRIENDS]-()-[r2:FRIENDS]-(friend_of_a_friend) RETURN friend_of_a_friend.name AS fofName
S9 : 补充语法
// 修改节点属性
MATCH (a:User {name:'会员A'}) SET a.city="武汉"
// 删除节点属性
MATCH (a:User {name:'会员A'}) REMOVE a.city
// 删除节点
MATCH (a:User {name:'会员G'}) DELETE a
// 删除关系
MATCH (a:User {name: '会员G'})-[r:FRIENDS]-(b:User {name: '会员D'}) DELETE r
// 删除节点及关系
MATCH (alice:User {name: '会员G'}) DETACH DELETE alice
三. 性能对比
性能比较主要集中在特定的场景,图数据库因为其特殊的形式,在深度遍历等方面的效率是远大于关系型数据库的,取自Neo4j 官方的博文 : @ http://neo4j.com/news/how-mu…
在 一百万数据 里面抽取 1000 个样本进行计算,查询朋友的朋友,按照不同的深度进行查询。
对于简单的好友的好友查询,Neo4j 比 MySQL 快 60%。对于朋友的朋友的朋友来说,Neo 的速度快了 180 倍。对于深度四查询,Neo4j 的速度快了 1,135 倍
四. 业务上怎么使用它更受益
在此期间比较浅显的读了一下 Neo4j 实战,大概可以从这几个方面来描述一下图数据库的优势 :
4.1 图数据库的使用场景
想看使用场景我们直接去官方看案例即可 :
我打交道比较多的就是社交和零散,主要看看这两块
社交关系
不知道大家有没有听过一个理论叫六度分隔理论,这个理论说的就是 : 世界上任意两个人之间的联系最多需要通过不超过六个中介,即通过六个人,就可以建立起联系。
我们假设这个理论上成立的,我想知道我和一个陌生人之间可以是通过哪几个人进行关联的,首先从 MySQL 角度看 :
- S1 : 构建一个人员信息表 , 为每个人员分配一个主键,标识现实中的实体
- S2 : 构建一个关联信息表,构建这个人会和哪些人员进行关联
- S3 : 走3个left join 或者逻辑中进行 for 循环+算法进行判断
很明显,这种方式占用资源很多,一不小心就会形成笛卡尔积,几何倍数
那么如果用图数据库,该怎么做?
// 展示会员A 朋友的朋友的朋友
MATCH (start:User {name: '会员A'})-[:FRIENDS*3]-(foaf:User) RETURN start, foaf
零售行业
最直接的包括最短路径算法,最优库存的实现 , 其次包括会员关系,精准有效都有些作用。
最短路径最直接的场景就是送外卖,哪些外卖分配更加合理。往更高级方向想就是物流。
总结
图数据库的使用是能解决很多复杂场景的问题的,但是这需要达到一定的量级。没达到量级使用关系型数据一样能满足。
但是整体来说,针对特定场景提高效率,优化业务,精准消费的效果还是很可观的。