图数据库是一种以图为数据结构来存储信息的数据库,不像我们通常所熟知的关系型数据库那样把数据存储在表(table)中,图数据库将数据存储在节点(node)和关系(relationship)里面,这种存储形式适用于数据之间的关系和数据本身一样重要的场景。
Neo4j是图数据库的典型代表,也是使用范围最广的图数据库。在Neo4j中,我们需要了解四个核心的概念:
1. Node(节点) 通常代表对象或实体。举例来说,在一个社交网络中,人物、位置、公司等都可以用节点来表示。
2. Label(标签)用来给节点做分类,每个分类就是一个标签。举例来说,张三、李四就是人物这个标签类,残友软件和残友智建就是公司这个标签。一个节点可以有多个标签,比如张三属于“人物”标签,如果他在残友软件上班,也可以打上“雇员”这个标签。
3. Relationship(关系)连接开始和结束两个节点的单向边,用来描述这两个节点之间的关系,一个关系只有一种类型(Type)。在“张三就职于残友软件”这个事实陈述中,“就职于”就是一条从节点“张三”指向“残友软件”的关系边。
4. Property(属性)键值对,用来进一步描述节点或关系的属性,每个节点或关系可以有任意数量的属性,而且同种类型节点的属性不必相同。仍然拿上面的例子,“张三”这个节点可以增加“性别:男;生日:1月1日”这两个属性,“就职于”这个关系可以增加“职位:软件工程师;开始时间:2024年4月”这两个属性。
类似于关系型数据库的SQL(Structured Query Language,结构化查询语言),图数据库也有自己的查询语言GQL,Cypher是Neo4j上的图查询语言实现,一般简称为CQL。
MATCH (m:Label) - [r:Type] -> (n:Label)WHERE m.Property AND/OR r.PropertyRETURN m, r, n
MERGE执行时会先查找结构是否已存在,避免重复创建。注意下面这两条CQL语句的区别,前者会查询“阿珍-爱上了->阿强”这条路径是否存在,这意味着即使数据库中存在“阿珍”这个节点,也会新建一个重复的。而后面那条语句不会重复创建。
MERGE (p:Person {name:"阿珍"})-[r:爱上了]->(q:Person {name:"阿强"})
MERGE (p:Person {name:"阿珍"}) MERGE (q:Person {name:"阿强"})MERGE (p)-[r:爱上了]->(q)
可以通过ON CREATE 和 ON MATCH自定义MERGE执行的逻辑,已有则触发ON MATCH,否则触发ON CREATE
MERGE (m:Movie {title: 'Rocketman'})ON CREATE SET m.createdAt = datetime()ON MATCH SET m.updatedAt = datetime()SET m.released = 2019RETURN m
3. DELETE 删除关系或节点,当删除的节点还存在和其他节点的关系时会报错,使用DETACH DELETE即可
6. CALL 查询全局信息,db.labels(), db.propertyKeys()
8. ANY 对列表中的“每一项值”进行判断(值判断);EXISTS 判断“整个结构是否存在”(结构判断)
要在Neo4j中建立现实世界的概念模型,要先识别出节点(一般对应需求描述中的名词)和关系(一般对应需求描述中的动词),然后遵循一些命名规范和设计原则建立起数据冗余小、有查询优化的图结构存储。
-
标签用驼峰法命名,例如Person, Company, GitHubRepo
-
关系用全大写,例如FOLLOWS,MARRIED_TO
-
属性的key用首字母小写的驼峰,比如deptId, firstName
2. 设计原则
-
数据去重:很多节点共有的属性值抽取成新的节点,减少数据冗余存储。比如下图(a)中,Movie节点的languages属性列表有重复,可以将其列表项抽取成新的节点,如图(b)所示。
-
关系细化:如果要加快关系查询,可以把关系拼接上属性值作为一个细化关系。比如下图中的ACTED_IN关系,原本有year和role两个属性,如果要查询year:1995的ACTED_IN关系,需要扫描所有ACTED_IN然后查看year属性是否为1995,效率很低。将“ACTED_IN”拼接year的属性值作为细化关系,查询某个年份的ACTED_IN就不必全部扫描了。