网易首页 > 网易号 > 正文 申请入驻

让我们用 SQL 开发一个图形数据库吧!

0
分享至

作者 | 不剪发的Tony老师 责编 | 欧阳姝黎

出品 | CSDN博客

图形数据库(Graph Database)是 NoSQL 数据库的一种,使用图结构来存储、表示、处理和查询数据。图是节点(Node)或者顶点(Vertice)和连接(Link)或者边缘(Edge)的集合。节点代表了一个实体(人、物体等),连接代表了两个节点之间的联系(好友、爱好等)。图形数据库非常适合社交关系分析、金融欺诈检测、知识图谱等领域,Neo4j 是著名的图形数据库。

除了使用专门的图形数据库之外,如今主流的关系型数据库都提供了 JSON 文档存储功能以及通用表表达式(WITH 子句)的支持。我们完全可以基于这些功能实现一个简单的图形数据库,同时可以使用 SQL 语句对图形进行操作和查询。

设计表结构

图要由两个结构组成:节点边缘

节点代表了一个实体对象,例如人、地点、事物等。节点可以拥有属性,例如人的姓名、性别等。一个节点对应了关系型数据库中的一行数据或者文档数据库中的一个文档。

边缘代表了两个对象之间的关系,例如某人住在某地。边缘也可以拥有属性,例如何时开始住在某地。一个边缘对应了关系型数据库中的一个外键记录或者文档数据库中的一个链接 id。

基于图的结构,我们可以在关系型数据库中创建两个表:node 和 edge。它们的 ERD 如下图所示:

创建以上表结构的 SQL 脚本如下(MySQL 语法):


-- MySQLCREATE TABLE IF NOT EXISTS node (node_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,properties JSON
CREATE TABLE IF NOT EXISTS edge (edge_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,source_id BIGINT NOT NULL,target_id BIGINT NOT NULL,properties JSON,FOREIGN KEY(source_id) REFERENCES node(node_id),FOREIGN KEY(target_id) REFERENCES node(node_id)
CREATE INDEX idx_edge_source_id ON edge(source_id);CREATE INDEX idx_edge_target_id ON edge(target_id);

对于节点表 node,我们创建了一个自增主键 node_id,以及一个存储节点属性的 JSON 字段 properties。对于边缘表 edge,我们创建了一个自增主键 edge_id,表示关系起点的字段 source_id 和表示关系终点的字段 target_id,以及一个存储关系属性的 JSON 字段 properties。

同时,为了数据操作和提高查询性能,我们创建了两个索引 idx_edge_source_id 和 idx_edge_target_id。

完整的表结构和查询脚本可以从 GitHub 或者 CODE CHINA 下载。除了 MySQL 之外,我们还提供了 Oracle、Microsoft SQL Server、PostgreSQL 以及 SQLite 版本。

实现图查询

插入数据

接下来我们使用 SQL 语句插入一些测试数据,首先插入几个节点:


INSERT INTO node(properties) VALUES('{"Label":"Person", "Name":"张三", "Age": 22}');INSERT INTO node(properties) VALUES('{"Label":"Person", "Name":"李四", "Age": 30}');INSERT INTO node(properties) VALUES('{"Label":"Person", "Name":"王五", "Age": 28}');

以上语句插入了 3 个节点,它们的 Label 都是 Person,拥有姓名和年龄属性。

然后我们再建立一些关系:


INSERT INTO edge(source_id, target_id, properties)VALUES((SELECT node_id FROM node WHERE json_value(properties, '$.Name')='张三'),(SELECT node_id FROM node WHERE json_value(properties, '$.Name')='李四'), '{"Label":"关注", "Degree": 80}');INSERT INTO edge(source_id, target_id, properties)VALUES((SELECT node_id FROM node WHERE json_value(properties, '$.Name')='李四'),(SELECT node_id FROM node WHERE json_value(properties, '$.Name')='王五'), '{"Label":"关注", "Degree": 90}');INSERT INTO edge(source_id, target_id, properties)VALUES((SELECT node_id FROM node WHERE json_value(properties, '$.Name')='张三'),(SELECT node_id FROM node WHERE json_value(properties, '$.Name')='王五'), '{"Label":"关注", "Degree": 60}');

其中,json_value 是 MySQL 提供的 JSON 函数,用于提取 JSON 文档中的元素。

以上测试数据建立的关系图如下所示:

对于节点和边缘的查询和普通表类似,例如:


SELECT properties FROM node WHERE json_value(properties, '$.Name')='张三';properties |{"Age": 22, "Name": "张三", "Label": "Person"}|
SELECT * FROM edge WHERE source_id = 1 AND target_id = 2;edge_id|source_id|target_id|properties |1| 1| 2|{"Label": "关注", "Degree": 80}|
图的遍历

我们从节点“张三”出发,遍历所有的节点:


WITH RECURSIVE traverse(id, relation, hops) AS (SELECT node_id, cast(json_value(properties, '$.Name') AS CHAR(100)), 0FROM nodeWHERE json_value(properties, '$.Name')='张三'UNION ALLSELECT target_id, CONCAT(relation,'->',json_value(n.properties, '$.Name')), hops + 1FROM edge eJOIN traverse t ON e.source_id = t.idJOIN node n ON e.target_id = n.node_idSELECT id, relation, hopsFROM traverse;
id|relation |hops|1|张三 | 0|2|张三->李四 | 1|3|张三->王五 | 1|3|张三->李四->王五| 2|

首先,我们定义了一个递归形式的通用表表达式 traverse,它由两部分组成,使用 UNION ALL 进行组合。第一个 SELECT 语句返回了起点“张三”,第二个 SELECT 语句引用了 traverse 自身,通过不停的迭代返回所有连接的节点。字段 hops 代表了节点之间的跳跃次数。最后的 SELECT 语句返回了全部的节点信息。

关于通用表表达式的语法和使用案例,可以参考《 实战 SQL: 实现百度、高德等地图中的地铁换乘线路查询 》 和 《实战 SQL:微信、微博等社交网络中的友好、粉丝关系分析》这两篇文章。

从遍历的结果可以看出,MySQL 默认采用的是广度优先搜索方法。如果想要实现深度优先搜索,可以使用 ORDER BY 排序:


WITH RECURSIVE traverse(id, relation, hops) AS (SELECT node_id, cast(json_value(properties, '$.Name') AS CHAR(100)), 0FROM nodeWHERE json_value(properties, '$.Name')='张三'UNION ALLSELECT target_id, CONCAT(relation,'->',json_value(n.properties, '$.Name')), hops + 1FROM edge eJOIN traverse t ON e.source_id = t.idJOIN node n ON e.target_id = n.node_idSELECT id, relation, hopsFROM traverseORDER BY relation;
id|relation |hops|1|张三 | 0|2|张三->李四 | 1|3|张三->李四->王五| 2|3|张三->王五 | 1|
最短距离

基于以上图的遍历,我们可以很容易地找出两个节点之间的最短距离。例如,以下语句可以找出“张三”和“王五”之间的最短距离:


WITH RECURSIVE traverse(id, relation, hops) AS (SELECT node_id, cast(json_value(properties, '$.Name') AS CHAR(100)), 0FROM nodeWHERE json_value(properties, '$.Name')='张三'UNION ALLSELECT target_id, CONCAT(relation,'->',json_value(n.properties, '$.Name')), hops + 1FROM edge eJOIN traverse t ON e.source_id = t.idJOIN node n ON e.target_id = n.node_idSELECT id, relation, hopsFROM traverseJOIN node ON id = node_idWHERE json_value(properties, '$.Name')='王五'ORDER BY hopsLIMIT 1;
id|relation |hops|3|张三->王五 | 1|


索引优化

我们使用 EXPLAIN 命令查看一下最短距离搜索语句的执行计划:


EXPLAINWITH RECURSIVE traverse(id, relation, hops) AS (SELECT node_id, cast(json_value(properties, '$.Name') AS CHAR(100)), 0FROM nodeWHERE json_value(properties, '$.Name')='张三'UNION ALLSELECT target_id, CONCAT(relation,'->',json_value(n.properties, '$.Name')), hops + 1FROM edge eJOIN traverse t ON e.source_id = t.idJOIN node n ON e.target_id = n.node_idSELECT id, relation, hopsFROM traverseJOIN node ON id = node_idWHERE json_value(properties, '$.Name')='王五'ORDER BY hopsLIMIT 1;
id|select_type|table |partitions|type |possible_keys |key |key_len|ref |rows|filtered|Extra |1|PRIMARY || |ALL | | | | | 6| 100.0|Using temporary; Using filesort |1|PRIMARY |node | |ALL |PRIMARY | | | | 3| 50.0|Using where; Using join buffer (hash join)|2|DERIVED |node | |ALL | | | | | 3| 100.0|Using where |3|UNION |t | |ALL | | | | | 3| 100.0|Recursive; Using where |3|UNION |e | |ref |idx_edge_source_id,idx_edge_target_id|idx_edge_source_id|8 |t.id | 1| 100.0| |3|UNION |n | |eq_ref|PRIMARY |PRIMARY |8 |hrdb.e.target_id| 1| 100.0| |

由于 node 表的 properties 字段中的 Name 元素缺少合适的索引,查询使用了大量的全表扫描,如果图中的节点数量很多时性能比较差。对此,我们可以使用数据库的函数索引为 JSON 文档中的节点数据创建索引,从而提高访问性能。例如:


CREATE INDEX idx_node_name ON node ( (json_value(properties, '$.Name')) );

执行以上命令创建索引之后,我们再次查看执行计划:


id|select_type|table |partitions|type |possible_keys |key |key_len|ref |rows|filtered|Extra |1|PRIMARY |node | |ref |PRIMARY,idx_node_name |idx_node_name|2051 |const | 1| 100.0|Using temporary; Using filesort |1|PRIMARY || |ref | |0> |9 |hrdb.node.node_id| 2| 100.0| |2|DERIVED |node | |ref |idx_node_name |idx_node_name|2051 |const | 1| 100.0| |3|UNION |t | |ALL | | | | | 2| 100.0|Recursive |3|UNION |e | |ALL |idx_edge_source_id,idx_edge_target_id| | | | 2| 50.0|Using where; Using join buffer (hash join)|3|UNION |n | |eq_ref|PRIMARY |PRIMARY |8 |hrdb.e.target_id | 1| 100.0| |

作者简介:不剪发的 Tony 老师,CSDN 博客专家,CSDN 学院签约讲师, GitChat 专栏作者。十余年数据库管理与开发经验。目前在一家全球性的游戏公司从事数据库架构设计和开发工作,擅长各种数据库管理与 SQL 开发,拥有Oracle OCP 和 Redhat RHCE 证书。

《新程序员001:开发者黄金十年》

2001 年创刊,20 年技术见证

人人都是开发者 家家都是技术公司

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
女生私处「小花瓣」长什么样,才正常?

女生私处「小花瓣」长什么样,才正常?

喜马拉雅主播暮霭
2024-06-09 13:13:48
河南小麦:亩产1825斤,评论区骂声一片。网友:没这数去哪领补贴

河南小麦:亩产1825斤,评论区骂声一片。网友:没这数去哪领补贴

三月柳
2024-06-19 11:02:13
意大利副总理:这些国家不签署瑞士和会公报,是“不愿破坏对俄关系”

意大利副总理:这些国家不签署瑞士和会公报,是“不愿破坏对俄关系”

参考消息
2024-06-19 09:12:09
不知几斤几两的哈马斯悲剧了,以色列:生擒哈尼亚,挖出指使者

不知几斤几两的哈马斯悲剧了,以色列:生擒哈尼亚,挖出指使者

娱宙观
2024-05-08 09:17:17
中国发出警告:90天内不支付358亿赔偿金,18艘军舰就别想要了

中国发出警告:90天内不支付358亿赔偿金,18艘军舰就别想要了

星辰故事屋
2024-06-09 17:09:59
无限期离队!桃色纠纷还在延续:郭昊文彻底回不来了?

无限期离队!桃色纠纷还在延续:郭昊文彻底回不来了?

狼叔评论
2024-06-19 12:33:08
女子公园举牌找老公,谁愿意养我两个儿子,我就愿意嫁给他

女子公园举牌找老公,谁愿意养我两个儿子,我就愿意嫁给他

四象八卦
2024-06-15 07:15:10
“一滩水”还是“一摊水”?上海中考作文用错字了吗?专家解答

“一滩水”还是“一摊水”?上海中考作文用错字了吗?专家解答

新民晚报
2024-06-18 20:46:53
日本三井化学宣布量产新一代CNT光刻薄膜,支持ASML新一代光刻机

日本三井化学宣布量产新一代CNT光刻薄膜,支持ASML新一代光刻机

IT之家
2024-06-18 15:59:20
国足18强赛分组抽到上上签!亚足联送来大礼包,6分将提前到手

国足18强赛分组抽到上上签!亚足联送来大礼包,6分将提前到手

小鬼头体育
2024-06-19 09:39:13
“哥哥硬吗”,女儿国国王满嘴虎狼之词,这谁顶得住

“哥哥硬吗”,女儿国国王满嘴虎狼之词,这谁顶得住

一个岛岛
2024-06-16 16:37:59
曝华为考虑在鸿蒙应用商店收取20%佣金,低于苹果和谷歌

曝华为考虑在鸿蒙应用商店收取20%佣金,低于苹果和谷歌

金融界
2024-06-18 20:13:19
吴清:对“造假者”和“配合的造假者”将立即查处

吴清:对“造假者”和“配合的造假者”将立即查处

财联社
2024-06-19 10:07:25
943元!8小时17分到深圳,本月22号开通!

943元!8小时17分到深圳,本月22号开通!

山西达人
2024-06-19 12:45:05
女儿中考亲妈后妈共同来接!网友:看了颜值后,丈夫才是人生赢家

女儿中考亲妈后妈共同来接!网友:看了颜值后,丈夫才是人生赢家

垛垛糖
2024-06-18 12:47:49
茅台宝马奥迪价格大跳水,小米销量断崖下跌,背后暗藏资本大骗局

茅台宝马奥迪价格大跳水,小米销量断崖下跌,背后暗藏资本大骗局

拾叁生意经
2024-06-18 18:40:08
凯特王妃为什么带病复出!根据英媒报道,似乎有了答案!

凯特王妃为什么带病复出!根据英媒报道,似乎有了答案!

杂谈空间社
2024-06-18 23:45:26
真硬啊!膝盖半月板撕裂!刚拿到总冠军,他宣布手术

真硬啊!膝盖半月板撕裂!刚拿到总冠军,他宣布手术

篮球教学论坛
2024-06-18 15:15:09
是骡子是马拉出来遛遛:天才中专生姜萍被疑作弊,数学月考仅85分

是骡子是马拉出来遛遛:天才中专生姜萍被疑作弊,数学月考仅85分

瑜说还休
2024-06-17 12:19:02
杨得志卸任总参谋长,秘书提醒他:新总长比您小20岁,不用去接机

杨得志卸任总参谋长,秘书提醒他:新总长比您小20岁,不用去接机

史源历史专栏
2024-06-18 15:35:02
2024-06-19 15:02:44
CSDN
CSDN
成就一亿技术人
24733文章数 241821关注度
往期回顾 全部

科技要闻

英伟达超越苹果、微软登顶全球新股王

头条要闻

普京访问朝鲜前撤换4位副防长 4人都与绍伊古共事多年

头条要闻

普京访问朝鲜前撤换4位副防长 4人都与绍伊古共事多年

体育要闻

欧洲杯最大的混子,非他莫属

娱乐要闻

黄一鸣“杀疯了” 直播间卖大葱养孩子

财经要闻

吴清:证监会将推出“科创板八条”

汽车要闻

双肾格栅变化大/内饰焕新 新一代宝马X3官图发布

态度原创

亲子
时尚
数码
艺术
游戏

亲子要闻

女儿第一天上幼儿园看到爸爸来接她时,泪眼垂眸 这眼神谁顶得住

40岁女人的穿搭法则,帮你找回“精致美”,穿衣其实很简单

数码要闻

旧电脑不要换盆儿,0元变身游戏机

艺术要闻

穿越时空的艺术:《马可·波罗》AI沉浸影片探索人类文明

PS港服年中优惠来袭!部分游戏低至25折 7月3日结束

无障碍浏览 进入关怀版