Neo4j 与 Cypher 查询语言完全指南

Neo4j 从零到实战:安装部署、Cypher CRUD 操作、MATCH 模式匹配、路径查询、聚合分析、索引与约束、性能调优。含完整代码示例与查询技巧。

导语:图数据库的普及使者

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 查询语言的设计哲学:

  1. 模式即查询:(A)-[:R]->(B) 既是数据结构描述,也是查询目标
  2. 声明式遍历:只需描述"要找什么",无需指定"怎么遍历"
  3. 关系方向敏感:-> 和 <- 精确控制遍历方向
  4. 可变长度路径:*1..3 让多层关联查询简洁优雅

Neo4j 的性能优化要点:

  • 为常用查询属性建立索引
  • 使用参数化查询避免重复解析
  • PROFILE 分析执行计划,关注是否命中索引
  • 大量导入时使用 neo4j-admin import(离线批量导入)

延伸阅读:

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「graphdb」更多文章

  1. 查询缓存与物化视图
  2. 图数据测试策略与回归验证
  3. 图数据库并发控制与批量更新