导语:图数据库的普及使者
Neo4j 是目前最流行、生态最成熟的属性图数据库。它不仅是技术的实现,更推广了图思维的普及——从 2000 年发布至今,Neo4j 已经在欺诈检测、推荐系统、知识图谱、网络安防等领域积累了大量生产级案例。
Cypher 是 Neo4j 专用的声明式查询语言,它的设计哲学极其直观:用 ASCII 艺术描述图模式(Graph Pattern),让查询语句看起来像数据结构本身。
一句话总结:Neo4j + Cypher 是图数据库领域的"黄金组合"——最成熟的引擎配上最直观的查询语言。
1. 安装与运行
1.1 Docker 快速启动
# 拉取并运行 Neo4j
docker run \
--name neo4j \
-p 7474:7474 -p 7687:7687 \
-e NEO4J_AUTH=neo4j/password \
-e NEO4J_PLUGINS='["apoc", "gds"]' \
neo4j:5.15-community
# 访问
# HTTP UI: http://localhost:7474
# Bolt 协议: bolt://localhost:7687
1.2 数据浏览器
打开 http://localhost:7474,使用用户名 neo4j 和密码 password 登录。界面左侧输入 Cypher 查询,右侧可视化返回的图数据。
// 第一个查询:创建两个人
CREATE (a:Person {name: 'Alice', age: 28})
CREATE (b:Person {name: 'Bob', age: 32})
CREATE (a)-[:KNOWS {since: 2019}]->(b)
RETURN a, b
一句话总结:Neo4j 的 Docker 镜像 5 分钟开箱即用,浏览器内置的可视化引擎是理解图数据的最好工具。
2. Cypher 核心语法
2.1 CREATE:创建节点和关系
// 创建单个节点
CREATE (:Person {name: "Alice", age: 28})
// 创建多个节点和关系(原子操作)
CREATE
(alice:Person {name: "Alice", age: 28}),
(bob:Person {name: "Bob", age: 32}),
(carol:Person {name: "Carol", age: 25}),
(alice)-[:KNOWS {since: 2019}]->(bob),
(bob)-[:KNOWS {since: 2020}]->(carol),
(alice)-[:KNOWS {since: 2021}]->(carol)
// 创建时获取引用
CREATE (p:Product {name: "iPhone 15", price: 7999})
RETURN p
2.2 MATCH:模式匹配(查询核心)
// 查询所有 Person
MATCH (p:Person)
RETURN p.name, p.age
// 查询有关系的节点(找到 Alice 的朋友)
MATCH (alice:Person {name: "Alice"})-[:KNOWS]->(friend)
RETURN friend.name, friend.age
// 查询关系属性
MATCH (a:Person)-[r:KNOWS]->(b:Person)
WHERE a.name = "Alice"
RETURN b.name, r.since
// 查询双向关系(匹配两个方向的朋友)
MATCH (a:Person)-[:KNOWS]-(b:Person)
WHERE a.name = "Alice"
RETURN b.name
// 查询任意关系类型
MATCH (a:Person {name: "Alice"})-[r]->(b)
RETURN type(r), b.name
2.3 WHERE:条件过滤
// 基本比较
MATCH (p:Person)
WHERE p.age >= 25 AND p.age <= 35
RETURN p.name, p.age
// 字符串匹配
MATCH (p:Person)
WHERE p.name STARTS WITH "A"
RETURN p.name
// 正则(性能较差,小数据集可用)
MATCH (p:Person)
WHERE p.name =~ "A.*"
RETURN p.name
// 列表包含
MATCH (p:Person)
WHERE p.city IN ["Beijing", "Shanghai"]
RETURN p.name, p.city
// 存在属性
MATCH (p:Person)
WHERE p.email IS NOT NULL
RETURN p.name, p.email
2.4 RETURN:结果投影
// 返回节点
MATCH (p:Person)
RETURN p
// 返回属性
MATCH (p:Person)
RETURN p.name AS name, p.age AS age
// 返回计算值
MATCH (p:Person)
RETURN p.name, p.age * 12 AS months_alive
// 返回关系类型
MATCH ()-[r]->()
RETURN DISTINCT type(r) AS relationship_type
// 返回路径
MATCH path = (a:Person {name: "Alice"})-[:KNOWS*1..3]->(b:Person)
RETURN path
一句话总结:Cypher 的语法核心围绕四个动词展开——CREATE(建)、MATCH(查)、WHERE(滤)、RETURN(返),模式匹配用 ASCII 箭头直观表达关系方向。
3. 高级查询
3.1 路径查询
// 固定长度路径(2 跳)
MATCH (a:Person {name: "Alice"})-[:KNOWS*2]->(fof:Person)
RETURN DISTINCT fof.name
// 可变长度路径(1 到 3 跳)
MATCH (a:Person {name: "Alice"})-[:KNOWS*1..3]->(reachable:Person)
RETURN DISTINCT reachable.name
// 最短路径
MATCH path = shortestPath(
(a:Person {name: "Alice"})-[:KNOWS*]-(b:Person {name: "David"})
)
RETURN path, length(path) AS hops
// 所有最短路径(A* 算法)
MATCH path = allShortestPaths(
(a:Person {name: "Alice"})-[:KNOWS*]-(b:Person {name: "David"})
)
RETURN path
3.2 聚合分析
// 统计每个人都有多少个朋友
MATCH (p:Person)-[:KNOWS]->(friend)
RETURN p.name, count(friend) AS friend_count
ORDER BY friend_count DESC
// 平均年龄
MATCH (p:Person)
RETURN avg(p.age) AS avg_age, min(p.age) AS youngest, max(p.age) AS oldest
// 按城市分组统计
MATCH (p:Person)
RETURN p.city AS city, count(*) AS count, avg(p.age) AS avg_age
ORDER BY count DESC
// 朋友最多的前 10 人
MATCH (p:Person)-[:KNOWS]->(friend)
RETURN p.name, count(friend) AS degree
ORDER BY degree DESC
LIMIT 10
3.3 更新操作
// SET:更新属性
MATCH (p:Person {name: "Alice"})
SET p.age = 29, p.city = "Shanghai"
RETURN p
// REMOVE:移除属性
MATCH (p:Person {name: "Alice"})
REMOVE p.temp_field
// DELETE:删除节点(必须先删除关系)
MATCH (p:Person {name: "Alice"})-[r]-()
DELETE r, p
// DETACH DELETE:自动删除节点及其所有关系
MATCH (p:Person {name: "Alice"})
DETACH DELETE p
// MERGE:如果不存在则创建,存在则匹配(幂等)
MERGE (p:Person {name: "Alice"})
ON CREATE SET p.created = datetime()
ON MATCH SET p.updated = datetime()
RETURN p
一句话总结:Cypher 的路径查询让"朋友的朋友"这种多层关联查询变得像描述一句话一样自然,聚合函数和 SQL 类似但作用在图遍历结果上。
4. 索引与约束
4.1 索引
// 创建单属性索引
CREATE INDEX person_name FOR (p:Person) ON (p.name)
// 创建复合索引
CREATE INDEX person_name_age FOR (p:Person) ON (p.name, p.age)
// 文本索引(全文搜索)
CREATE FULLTEXT INDEX product_description FOR (p:Product) ON EACH [p.description]
// 查询使用全文索引
CALL db.index.fulltext.queryNodes("product_description", "iphone backup")
YIELD node, score
RETURN node.name, score
4.2 约束
// 唯一约束(确保每个 Person 的 email 唯一)
CREATE CONSTRAINT person_email FOR (p:Person)
REQUIRE p.email IS UNIQUE
// 节点属性存在约束
CREATE CONSTRAINT person_name_exists FOR (p:Person)
REQUIRE p.name IS NOT NULL
// 关系类型约束(需要企业版)
4.3 执行计划分析
// 查看查询执行计划
EXPLAIN MATCH (p:Person {name: "Alice"})-[:KNOWS]->(friend)
RETURN friend.name
// 查看查询并实际执行,返回统计
PROFILE MATCH (p:Person {name: "Alice"})-[:KNOWS*1..3]->(fof)
RETURN DISTINCT fof.name
一句话总结:Neo4j 的索引和约束机制与关系型数据库类似,但由于图查询以模式匹配为主,合适的索引能显著降低初始节点定位的时间。
5. 与应用程序集成
5.1 Python 驱动
from neo4j import GraphDatabase
class Neo4jService:
def __init__(self, uri, user, password):
self.driver = GraphDatabase.driver(uri, auth=(user, password))
def close(self):
self.driver.close()
def get_friends(self, name):
with self.driver.session() as session:
result = session.run(
"""
MATCH (p:Person {name: $name})-[:KNOWS]->(friend)
RETURN friend.name AS name, friend.age AS age
""",
name=name
)
return [record.data() for record in result]
def create_person(self, name, age, city):
with self.driver.session() as session:
session.run(
"""
CREATE (p:Person {name: $name, age: $age, city: $city})
""",
name=name, age=age, city=city
)
# 使用
service = Neo4jService("bolt://localhost:7687", "neo4j", "password")
friends = service.get_friends("Alice")
print(friends) # [{'name': 'Bob', 'age': 32}, ...]
service.close()
5.2 Go 驱动
package main
import (
"context"
"fmt"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)
func main() {
ctx := context.Background()
driver, err := neo4j.NewDriverWithContext(
"bolt://localhost:7687",
neo4j.BasicAuth("neo4j", "password", ""),
)
if err != nil {
panic(err)
}
defer driver.Close(ctx)
result, err := neo4j.ExecuteQuery(ctx, driver,
`MATCH (p:Person {name: $name})-[:KNOWS]->(f) RETURN f.name AS name`,
map[string]any{"name": "Alice"},
neo4j.EagerResultTransformer,
)
if err != nil {
panic(err)
}
for _, record := range result.Records {
fmt.Println(record.AsMap())
}
}
一句话总结:Neo4j 提供了所有主流语言的驱动(Python/Java/Go/JS/.NET),Bolt 协议二进制高效,参数化查询天然防注入。
6. 总结
Cypher 查询语言的设计哲学:
- 模式即查询:
(A)-[:R]->(B)既是数据结构描述,也是查询目标 - 声明式遍历:只需描述"要找什么",无需指定"怎么遍历"
- 关系方向敏感:
->和<-精确控制遍历方向 - 可变长度路径:
*1..3让多层关联查询简洁优雅
Neo4j 的性能优化要点:
- 为常用查询属性建立索引
- 使用参数化查询避免重复解析
- PROFILE 分析执行计划,关注是否命中索引
- 大量导入时使用
neo4j-admin import(离线批量导入)
延伸阅读:
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。