
图数据库这四个字这几年被提得太频繁以至于很多人第一次接触时脑子里冒出来的其实是数据库 er 图。这个误会我见得太多了——ER 图是画给关系型数据库看的实体、属性、联系规规矩矩三件套而图数据库里长的是另一套东西顶点、边、属性、标签边本身还能带属性和方向。两套模型长得像用起来完全是两种思路。我在三个项目里做过图数据选型最长的一次跑过十亿级点边、十几个 TB 的图最短的一次只用两台机器验一个 POC。前后试过 Neo4j、JanusGraph、NebulaGraph、HugeGraph、TigerGraph、ArangoDB、Dgraph 这七种踩过的坑从导入三天没跑完到集群扩了节点反而更慢都有。这篇就把当时那份对比笔记重新整理一遍讲清楚它们各自适合什么场景、上手难度差在哪、部署运维要提前想好什么。做风控、社交关系、知识图谱、数据血缘、IT 运维拓扑的朋友以及还在纠结要不要上图数据库的人都能从里面挑到能直接用的东西。1. 先把概念掰开图数据库是什么ER 图又是什么1.1 图数据库解决的是关系密集问题先说清楚它到底解决什么问题。关系型数据库擅长的是行和列也就是结构规整、单表查询为主的场景。订单表、用户表、商品表各存各的需要的时候 JOIN 一下。问题在于 JOIN 的代价随深度增长得非常快两三层还好五六层就可能把数据库拖垮。而现实里很多问题天然就是深度的这个人认识谁谁又认识谁中间隔几层能找到目标人物一笔资金经过几道流转最终进了哪个账户一个字段从哪张表算出来又喂给了哪张报表。这类问题用图来表达最自然的做法就是顶点代表实体、边代表关系。查询的时候不再是表之间反复 JOIN而是从一个顶点出发沿着边往外走。走一步就是一次指针跳转代价基本恒定所以多跳查询的耗时增长是接近线性的而不是指数级爆炸。这就是图数据库存在的根本理由不是什么时髦技术而是数据结构本身的匹配度问题。另外一个容易被忽略的点是建模灵活性。关系型数据库加一个字段要改表结构几亿行的大表改起来是件让人头疼的事。图数据库里给某一类顶点加个属性加就加了不影响其他顶点。业务模型还在快速变化的阶段这个优势非常实在。1.2 数据库 ER 图怎么画它和图模型差在哪既然热搜里有人在问什么是数据库 er 图和数据库 er 图怎么画这里顺手讲清楚因为它和图数据库的模型经常被混淆。ER 图的全称是实体-联系图本质是给关系型数据库做设计用的。经典画法也就是 Chen 记法里实体用矩形属性用椭圆联系用菱形主键属性给文字加下划线。一对多、多对多这些关系最终都要落成外键或者中间关联表也就是说 ER 图里的联系在物理实现上会变成一张真实的表。画 ER 图的步骤大致是先做需求梳理把所有名词挑出来当候选实体把动词挑出来当候选联系再确定每个实体的属性并标出主键然后确定联系类型一对一、一对多还是多对多最后做范式检查消除冗余。工具上draw.io 够轻量dbdiagram.io 可以直接写 DSL 出图PowerDesigner 适合大型项目做正向反向工程。图数据库的模型不一样。它用的是属性图Property Graph或者 RDF 三元组。属性图里联系是真实存在的一等公民就是边边可以带方向和属性比如(A)-[:转账 {金额: 5000, 时间: 2024-03-01}]-(B)。ER 图里的联系在物理层会消失变成外键属性图里的边在物理层是真实存储的这是两者最本质的区别。你如果把 ER 图直接搬成图模型会得到一张每张表一个标签、外键当边的图能跑但没发挥出图的价值因为属性都堆在顶点上了查询还是要靠过滤不是靠走边。真正实用的建图思路是先想清楚遍历路径也就是业务里最常见的查询是从哪走到哪然后围绕这些路径来设计顶点和边。想不清楚路径就建图最后大概率会变成用图数据库跑表格查询性能还不如原来。2. 七个候选的定位拆解与横向对比2.1 逐个说清楚这七个选手我不打算把市面上所有图数据库都列一遍那样只会让人更晕。下面这七个覆盖了主要的技术路线足够做决策参考了。Neo4j这个领域里最出名的选手原生图存储从底层开始就是为图设计的。查询语言是 Cypher用 ASCII 画图的方式写查询可读性非常好MATCH (a:Person)-[:KNOWS]-(b) RETURN b.name这种写法第一次看基本就能懂。事务是完整的 ACID社区版免费但只支持单机企业版才有集群。它的生态是最全的Bloom 可视化、APOC 工具库、GDS 图算法库都很成熟。短板是水平扩展能力写扩展到现在也谈不上特别顺畅超大规模场景要慎重。JanusGraph定位是图计算层和存储层分离。它自己没有存储引擎后端可以接 HBase、Cassandra、BerkeleyDB索引后端接 Elasticsearch、Solr 或 Lucene。查询语言走 Apache TinkerPop 的 Gremlin属于命令式的遍历 DSL表达能力强但学习曲线陡。它的优势是能白嫖大数据生态HBase 集群有多大它就能撑多大劣势是组件太多部署一套下来没有半天起步基本搞不定调优也分散在好几个系统里出问题时排查链路很长。NebulaGraph国产开源里做得比较扎实的一个shared-nothing 架构集群分 metad、graphd、storaged 三类角色存储层用 RocksDB靠 Raft 做一致性。查询语言叫 nGQL走的是类 SQL 的路线GO 3 STEPS FROM user_1 OVER follow YIELD follow.name这种写法对 SQL 背景的人很友好。它的卖点是水平扩展和超大规模几万亿条边的场景有实际案例。短板是生态相对 Neo4j 薄一些图算法的成熟度这些年才补上来。HugeGraphApache 孵化项目定位偏轻量好上手。支持 Gremlin也提供了自己的 REST API后端可以接 RocksDB、HBase、Cassandra、MySQL。它有一套配套的组件HugeGraph-Computer 做图计算Hubble 做可视化Loader 做数据导入。适合中等规模、团队人手不多、想快速看到效果的场景。TigerGraph走的是 MPP 并行路线查询语言 GSQL 是类 SQL 的支持累加器、多轮迭代这类写法做深度链接分析和图算法的性能很强。它是商业产品免费版有顶点数和边的限制超了就得买授权。如果你要做的是跑几十跳的路径分析或者全图跑社区发现它是有优势的但成本要提前算清楚。ArangoDB多模型数据库文档、键值、图三合一查询语言 AQL 也是类 SQL 风格。它的好处是一套数据库解决多种需求你既可以用它存 JSON 文档也可以做图遍历省去了多套系统之间的数据同步。原生图遍历性能不错集群用 Agency 做协调。适合业务模型混合、不想维护多套存储的团队。Dgraph原生分布式底层是自研的 Badger 存储用 Raft 做分片复制角色分 Alpha 和 Zero。查询语言是 DQL早期叫 GraphQL-写起来像 GraphQL还支持 GraphQL schema 直接映射。它的定位是为 GraphQL 生态服务的图数据库前端同学上手会比较快。需要注意它的社区版本和云服务经历过一些方向调整选型时最好确认一下当前的维护状态和长期路线。2.2 一张表看清七种图数据库的差异语言描述再多不如一张表直观。下面这个对比是我自己整理的维度选的是实际选型时最关心的几项。数据库开源情况查询语言存储引擎扩展方式事务上手难度典型场景Neo4j社区版免费Cypher原生图存储读副本为主写扩展有限ACID 完整低知识图谱、推荐、中小规模风控JanusGraphApache 开源GremlinHBase/Cassandra/BDB依赖后端水平扩展依赖后端高超大规模、已有大数据栈NebulaGraph开源nGQLRocksDB原生分片水平扩展支持快照隔离中万亿级点边、社交、风控HugeGraphApache 开源Gremlin RESTRocksDB/HBase/MySQL依赖后端支持中低中等规模、快速验证TigerGraph商业GSQL原生并行图MPP 原生ACID中高深度链接分析、图算法密集ArangoDB开源 商业AQL自研文档图原生分片ACID中多模型混合业务Dgraph开源DQLBadgerRaft 原生分片ACID中GraphQL 生态、分布式图这张表里最容易看走眼的是扩展方式这一列。写依赖后端水平扩展的意思是数据库本身的扩展能力完全取决于你选的 HBase 或 Cassandra 集群图这一层其实是个计算和查询的中间层写原生分片的是它自己就管分片和副本你不用额外维护一套存储系统。这两种路线的运维成本差得非常明显一个是部署一套图数据库另一个是部署一套图数据库加一套分布式存储加一套搜索引擎。3. 建模与查询语言实操同一张图怎么写3.1 用属性图把人-好友-城市建出来为了让后面的对比有共同基础我设计一个最小但能说明问题的模型人Person、城市City两类顶点好友FRIEND、居住LIVES_IN两类边。数据量不用大几十万顶点、几百万边就能看出语言差异。Cypher 建数据长这样CREATE (a:Person {name: 张三, age: 30}) CREATE (b:Person {name: 李四, age: 28}) CREATE (c:City {name: 杭州}) CREATE (a)-[:FRIEND {since: 2019}]-(b) CREATE (a)-[:LIVES_IN {since: 2015}]-(c);Gremlin 的等价写法是g.addV(Person).property(name, 张三).property(age, 30).as(a) .addV(Person).property(name, 李四).property(age, 28).as(b) .addV(City).property(name, 杭州).as(c) .addE(FRIEND).from(a).to(b).property(since, 2019) .addE(LIVES_IN).from(a).to(c).property(since, 2015) .iterate()nGQL 需要用 INSERT 语句INSERT VERTEX Person(name, age) VALUES p1:(张三, 30), p2:(李四, 28); INSERT VERTEX City(name) VALUES c1:(杭州); INSERT EDGE FRIEND(since) VALUES p1-p2:(2019); INSERT EDGE LIVES_IN(since) VALUES p1-c1:(2015);AQL 的写法偏文档风格INSERT { _key: p1, name: 张三, age: 30 } INTO Person INSERT { _key: c1, name: 杭州 } INTO City INSERT { _from: Person/p1, _to: Person/p2, since: 2019 } INTO FRIEND写完这几段你应该已经能感觉到差异了。Cypher 是声明式的你描述图应该长什么样Gremlin 是命令式的你描述一步一步怎么做nGQL 和 AQL 都是类 SQL对有关系型背景的人最友好。这不是审美问题而是直接影响团队上手速度的因素。如果团队里 SQL 背景的人占多数nGQL 或者 AQL 的培训成本会低很多如果团队里有人写过 Groovy 或者函数式代码Gremlin 反而会觉得很顺手。3.2 五种查询语言的等价写法对比接着查一个具体问题找出张三的朋友里也住在杭州的人按年龄倒序。CypherMATCH (a:Person {name: 张三})-[:FRIEND]-(b:Person)-[:LIVES_IN]-(c:City {name: 杭州}) RETURN b.name, b.age ORDER BY b.age DESC;Gremling.V().has(Person, name, 张三).out(FRIEND) .where(out(LIVES_IN).has(name, 杭州)) .order().by(age, desc) .valueMap(name, age)nGQLGO FROM p1 OVER FRIEND YIELD FRIEND._dst AS friend_id | GO FROM $-.friend_id OVER LIVES_IN WHERE LIVES_IN._dst c1 YIELD $-.friend_id AS pid | FETCH PROP ON Person $-.pid YIELD properties(vertex).name, properties(vertex).age注意 nGQL 用的是管道|上一句的输出作为下一句的输入这个设计思路和 Shell 管道一脉相承。第一次写会觉得别扭写熟了会发现挺好调试的因为每一段都可以单独拿出来跑。AQLFOR p IN Person FILTER p.name 张三 FOR friend IN 1..1 OUTBOUND p FRIEND FOR city IN 1..1 OUTBOUND friend LIVES_IN FILTER city.name 杭州 SORT friend.age DESC RETURN { name: friend.name, age: friend.age }GSQL 的写法是另一套风格要先定义查询CREATE QUERY friends_in_city(STRING name, STRING city) FOR GRAPH MyGraph { Start {Person.*}; Result SELECT b FROM Start:a -(FRIEND)- Person:b -(LIVES_IN)- City:c WHERE a.name name AND c.name city; PRINT Result; }这里有个实际体会Cypher 和 AQL 的写法最接近人怎么想就怎么写Gremlin 需要你把思路拆成流水线步骤写复杂查询时反而更可控因为每一步都可以中断查看中间结果nGQL 的管道设计让多跳查询写起来很紧凑但嵌套深了可读性会下降GSQL 需要预先定义好处是查询可以复用、可以调优坏处是改一次逻辑要重新编译。3.3 多跳与路径查询的写法差异图数据库最能打的地方是多跳。找张三到王五之间 4 跳以内的最短路径各家写法差别更大。Cypher 用shortestPathMATCH (a:Person {name: 张三}), (b:Person {name: 王五}), p shortestPath((a)-[:FRIEND*..4]-(b)) RETURN p;Gremlin 用repeat().times()g.V().has(Person, name, 张三) .repeat(both(FRIEND).simplePath()).times(4) .has(name, 王五).path().limit(1)NebulaGraph 提供FIND SHORTEST PATHFIND SHORTEST PATH FROM p1 TO p2 OVER FRIEND UPTO 4 STEPS;这里有个坑要提前说。多跳查询的性能极度依赖数据分布。如果某个顶点的度数特别高比如一个明星账号有几百万粉丝遍历到它的时候就会产生超级节点问题一次查询要展开上百万条边。七种数据库里原生图存储的Neo4j、TigerGraph在这类场景下靠的是把邻接表存在一起比外部存储的略好一些但真正要解决靠的是建模时把超级节点打散或者在查询里加度数限制。我一般会在压测数据集里专门埋几个度超过十万的顶点看看各家在极端情况下的表现这比平均值有意义得多。4. 部署实操从 Docker 单机到集群4.1 Docker 快速起一个最小验证环境做 POC 阶段别一上来就上集群。用 Docker 单机把核心查询跑通数据模型验证一遍再决定要不要投人力做集群部署。我一般用下面这种最简配置version: 3 services: neo4j: image: neo4j:5.20-community ports: - 7474:7474 - 7687:7687 environment: - NEO4J_AUTHneo4j/test12345 - NEO4J_server_memory_heap_initial__size2G - NEO4J_server_memory_heap_max__size4G - NEO4J_server_memory_pagecache_size4G volumes: - ./neo4j/data:/data - ./neo4j/logs:/logsNebulaGraph 的最小环境要起三类角色官方有个 docker-compose 模板大致是 metad 一个、graphd 一个、storaged 一个加上 console 客户端。要注意的是 graphd 的内存配置默认给的值偏小跑大批量导入容易 OOMdocker run -itd --name nebulagraph \ -p 9669:9669 -p 19669:19669 \ -e NEBULA_ALLOW_LIST0.0.0.0 \ vesoft/nebula-graph:3.8HugeGraph 和 ArangoDB 的启动相对简单前者一个容器加一个配置好的后端就够后者单容器就能跑图遍历。JanusGraph 我不建议用 Docker 做 POC因为它要拉 HBase 或者 Cassandra 加 Elasticsearch一整套起来没有三四个容器下不来验证效果还不如找一台虚拟机手动装一套看得清楚。这里有个血泪教训Docker 环境验证阶段千万不要用默认的内存参数去跑真实数据量。我曾经用默认配置导入两千万边跑到一半容器被杀查了半天以为是数据有问题最后发现是 JVM 堆开得太小。做 POC 的时候数据量可以小但内存参数要按正式环境的比例来配这样性能数据才有参考价值。4.2 集群角色与容量估算到正式部署这一步各家架构差异就非常明显了。Neo4j 的集群叫因果集群核心是三个 core 节点加若干 read replica。三个 core 用 Raft 选主做写操作read replica 只承担读流量。这意味着它的写能力受限于单个 core读能力可以靠加副本横向扩。所以如果你的场景是读多写少Neo4j 是够用的如果是持续高并发写入就要认真评估了。NebulaGraph 是标准的三角色metad 管元数据和集群调度一般部署 3 个graphd 是无状态的计算层可以随意加前面挂负载均衡storaged 存数据按分片划分一般至少 3 个做副本。扩 storaged 节点的时候会触发数据均衡这个过程会占用网络带宽最好放在业务低峰期做。HugeGraph 和 JanusGraph 的集群能力其实是后端给的你配 HBase 就是 HBase 的扩展能力配 Cassandra 就是 Cassandra 的能力。所以容量规划的时候主要精力要花在后端存储上图这一层的调优空间其实不大。容量估算给个粗算方法。假设 5 亿个顶点平均每个顶点 3 个属性每个属性序列化后 50 字节那顶点属性大约 75 GB。20 亿条边每条边带 2 个属性约 100 字节那是 200 GB。加上索引通常再乘以 1.3 到 1.5 的系数总量在 400 GB 上下。再考虑副本数三副本就是 1.2 TB 的裸存储。内存方面热数据要能放下索引和一部分邻接数据一般建议总内存不低于存储量的 1/10也就是 120 GB 内存起步。这个算法很粗糙但足够让你在采购机器的时候心里有个数不至于差一个数量级。4.3 Neo4j Bloom 的安装与布隆过滤器的两个bloom热搜里出现了图数据库 bloom 安装这个词其实对应两个完全不同的东西得分开说。第一个是Neo4j Bloom这是 Neo4j 官方的图可视化探索工具。它的定位是给业务人员用的不需要写 Cypher通过搜索框和自然语言式的输入就能探索图比如输入张三的朋友就能出图。安装上有几个要点版本要和数据库对齐Bloom 5.x 配 Neo4j 5.x在 Neo4j Desktop 里可以直接从 Graph Apps 面板装服务器部署的话把对应的 jar 包放进plugins目录重启服务然后通过http://主机:7474/bloom访问。要注意的是 Bloom 属于企业版功能社区版用不了做技术选型的时候这一条要提前确认别等做完 Demo 才发现用不了。另外反向代理场景下要配好server.bolt.advertised_address和 HTTP 的转发头否则浏览器打开可能是白屏。第二个是布隆过滤器英文也是 Bloom filter这个跟可视化没关系是一种概率型数据结构用来判断某个元素一定不在集合里或者可能在集合里。它在图数据库里的作用是减少磁盘查找查询一条边之前先问布隆过滤器这个 key 可能存在吗如果回答不可能就直接跳过磁盘 IO。HBase 里可以通过建表时指定BLOOMFILTER ROW来开启JanusGraph 和 HugeGraph 用 HBase 作后端时会继承这个能力。实测下来在查询大量不存在的 key的场景下开启布隆过滤器能把 IO 次数降一个数量级但代价是额外的内存占用一般占存储的 1% 到 2%。如果你的查询很少命中不存在的 key那开不开差别不大别盲目开。5. 压测与性能验证拿数据说话5.1 数据生成与压测指标设计选型阶段最怕的就是感觉这个快。我一般会固定一套压测流程所有候选跑同一套数据才有可比性。数据生成用 LDBC 的社交网络数据集或者自己写脚本生成幂律分布的图。幂律分布很重要因为真实的社交网络、资金网络都是少数节点连接极多、多数节点连接很少用均匀分布生成的数据跑出来的性能是失真的。生成规模建议分三档一千万边、一亿边、十亿边。大多数团队其实撑不到十亿一亿就能看出差距了。压测查询要覆盖四类点查根据 ID 找顶点考察索引效率一跳查找出某人的所有朋友考察邻接表读取三跳查朋友的朋友的朋友考察遍历性能聚合统计某城市的朋友总数、最短路考察计算能力指标上除了 QPS 和 P99 延迟这两个常规项我会额外记两个数导入速度和内存增长率。导入速度决定你的项目周期一个每秒只能导 2 万边的库和一个每秒能导 50 万边的库在十亿边的数据集上差了将近一周时间。内存增长率决定你的机器成本有些库跑完一轮压测内存不释放第二轮直接 OOM这种问题在生产环境里是致命的。5.2 各类查询的瓶颈点位压测跑多了会发现不同数据库的瓶颈点完全不一样知道瓶颈在哪比知道谁快谁慢更有价值。数据库常见瓶颈表现缓解方向Neo4jJVM 堆与 page cache缓存命中率下降后延迟陡增调大 page cache控制堆大小JanusGraph后端存储与索引多跳查询慢索引配置错就全表扫建好复合索引调 ES 分片NebulaGraph分片均衡与网络扩容后短期性能下降低峰期做均衡控制分片数HugeGraph单机资源数据量上来后写放大明显后端换 HBase加缓存TigerGraph内存占用图规模受内存限制按内存规划分区控制加载量ArangoDB遍历深度深遍历时中间结果集膨胀限制遍历深度用剪枝条件Dgraph分片与副本同步写入放大跨分片查询慢调整分片键避免热点拿 Neo4j 举例它的 page cache 是专门用来缓存图数据的和 JVM 堆是分开的。很多人习惯把堆开得很大结果 page cache 给得很小磁盘 IO 一上来性能就崩。我的经验是堆不超过物理内存的 50%剩下的大部分留给 page cache前提是图数据总量能放进 page cache放不进就要考虑分片或者加机器了。这一条在官方文档里写得比较委婉但实际调优时的权重非常高。6. 常见问题与排查速查表6.1 安装与启动阶段部署阶段的问题往往最耗时间因为报错信息经常指向错误的方向。下面这些是我实际遇到过的。现象可能原因排查方式处理方式容器启动后立刻退出内存参数超过容器限制docker logs看 OOM 记录调小堆参数或提高容器内存上限端口通了但连不上监听地址绑定到 127.0.0.1netstat查看监听地址改成 0.0.0.0集群节点互相发现失败主机名解析或端口未开放各节点互相 ping 和 telnet统一 hosts 或使用域名导入中途卡住磁盘写满或 GC 停顿查看磁盘使用率和 GC 日志清理磁盘调整 GC 参数可视化界面白屏反向代理头缺失或版本不匹配浏览器控制台看请求失败项补全转发头对齐版本这里特别提一下版本不匹配这个坑。图数据库和它的生态组件版本耦合度很高Neo4j 大版本升级经常带来配置项改名比如 4.x 的dbms.memory.pagecache.size到 5.x 变成了server.memory.pagecache.size直接拿旧配置启动会静默忽略跑起来后性能差得莫名其妙查半天还以为是数据问题。6.2 查询与性能阶段查询阶段的排查更依赖执行计划。几乎所有图数据库都有类似EXPLAIN的能力Neo4j 用EXPLAIN和PROFILEnGQL 用PROFILEAQL 用explain。养成习惯任何超过一秒的查询先看执行计划别猜。现象可能原因排查方式处理方式点查很慢属性上没有建索引执行计划显示全扫描建索引注意索引要重建才生效多跳查询超时命中超级节点统计出发顶点度数打散超级节点或加度数限制内存持续增长查询结果集未释放观察多轮查询后的内存曲线分页返回限制返回条数写入抖动索引重建或压缩触发看写入延迟的时间分布错峰操作控制批量大小结果不一致副本同步延迟对比不同节点的查询结果调整一致性级别超级节点这个问题值得单独强调。它的典型特征是平均查询 50 毫秒但某些查询要 30 秒而且这些慢查询的出发点是固定的那几十个顶点。发现这个规律之后我的做法是在导入阶段就统计顶点度数分布把排行前 0.01% 的顶点单独拉出来看看是不是需要拆分成多个虚拟顶点比如按时间分片或者在查询层加白名单。一味加机器是解决不了的因为瓶颈在单个分片内。6.3 数据导入阶段导入是每个项目都绕不开的环节也是最容易出问题的环节。几个实操经验。批量大小控制在 1000 到 5000 条一批太小了网络开销大太大了内存容易爆这个值需要按实际测试调整不要照抄。导入前先建好索引还是导入后建索引取决于数据库Neo4j 建议先建约束再导入能提前拦截重复数据HBase 后端的则建议先导入再批量建索引速度差好几倍。另外导入脚本一定要支持断点续传别做成一次性全量跑因为十亿级的导入跑几个小时很正常中途断一次从头再来是件非常崩溃的事。还有一个很容易忽略的点导入期间不要开监控告警的秒级采集。图数据库在批量导入时 CPU 和内存会短暂冲高如果监控规则设得太敏感运维群里会响个不停最后大家都把告警静音了真正出问题的时候反而没人看。合理做法是导入期间单独设一套宽松阈值或者干脆把告警延后到导入结束。7. 选型结论与踩坑心得七个跑下来我不太相信最好的图数据库这种说法只相信最合适的。把当时的决策逻辑摊开来说大概是这样几条。团队能力优先于技术先进性。如果团队里没人维护过 HBase 和 Elasticsearch那 JanusGraph 就算性能数据再好看我也建议先放一放因为真正出问题的时候你连日志都找不到在哪。选型的本质是选一个出问题能修好的系统而不是选一个跑分最高的系统。数据规模决定架构路线。千万级点边Neo4j 或者 HugeGraph 单机加个从库就够了上一套分布式只会增加运维负担。上亿级可以考虑 NebulaGraph 或者 Neo4j 的读副本方案。十亿级以上原生分片的方案优势才真正体现出来。别为了以后可能变大提前上分布式绝大多数项目的数据量增长比预期慢得多。查询模式的复杂度决定语言选择。如果业务查询以两三跳为主Cypher 那种声明式语言开发效率最高如果需要动态拼接复杂的遍历逻辑Gremlin 的灵活性更有优势如果团队 SQL 背景浓厚nGQL 和 AQL 的培训成本最低。这一条看起来是小事但一个项目周期下来开发效率的差异会非常明显。至于那些看起来很小但反复踩的坑我列几条把 ER 图直接搬成图模型是最常见的设计错误建图之前一定要先想清楚遍历路径Bloom 可视化工具属于企业版能力别等做完演示才发现用不了布隆过滤器这种优化手段要按场景开不开没事、乱开占内存执行计划要当成日常工具用别等到性能出问题才想起来看还有导入脚本一定要支持断点续传这一条能省掉好几个通宵。如果非要用一句话概括这七种我会说Neo4j 是上手最快、生态最全的那个NebulaGraph 是国产里规模能力最扎实的那个JanusGraph 是把扩展性交给大数据栈的那个HugeGraph 是中等规模最省心的那个TigerGraph 是图算法性能最猛、但要花钱的那个ArangoDB 是只想维护一套存储的那个Dgraph 是 GraphQL 生态里最顺手的那个。选哪个先看你手上有多少人、多少机器、数据长什么样再回头来看这份对比表答案基本就浮出来了。