跳到主要内容

索引与约束

索引和约束是数据库性能优化和数据完整性保障的重要手段。本章介绍 Neo4j 中的索引类型、创建方法以及约束的使用。

索引概述

索引是数据库中用于加速数据查询的数据结构。在 Neo4j 中,索引可以显著提高基于属性值的查询性能。

什么时候需要索引

  • 频繁根据某个属性查询节点或关系
  • 属性值具有高基数(不同值较多)
  • 需要快速定位特定节点进行更新或删除

Neo4j 的索引类型

索引类型适用场景Neo4j 版本
B-tree 索引通用范围查询和精确匹配3.5+
全文索引文本搜索、模糊匹配3.5+
查找索引点查找(精确匹配)4.0+
范围索引范围查询4.0+
文本索引字符串前缀、后缀、包含查询4.0+
点索引空间数据查询4.0+

创建索引

单属性索引

-- 创建 B-tree 索引(默认)
CREATE INDEX person_name FOR (p:Person) ON (p.name)

-- 创建范围索引
CREATE RANGE INDEX person_age FOR (p:Person) ON (p.age)

-- 创建查找索引
CREATE LOOKUP INDEX person_lookup FOR (p:Person) ON EACH labels(p)

-- 创建文本索引
CREATE TEXT INDEX person_city FOR (p:Person) ON (p.city)

复合索引

-- 多字段索引
CREATE INDEX person_name_age FOR (p:Person) ON (p.name, p.age)

关系索引

-- 为关系创建索引
CREATE INDEX rel_since FOR ()-[r:FRIEND]-() ON (r.since)

管理索引

查看索引

-- 列出所有索引
SHOW INDEXES

-- 查看索引详情
SHOW INDEXES YIELD name, type, entityType, labelsOrTypes, properties

删除索引

-- 按名称删除索引
DROP INDEX person_name

-- 如果存在则删除
DROP INDEX person_name IF EXISTS

索引使用提示

-- 强制使用特定索引(不推荐常规使用)
MATCH (p:Person)
USING INDEX p:Person(name)
WHERE p.name = '张三'
RETURN p

约束

约束用于确保数据的完整性和一致性,防止不符合规则的数据进入数据库。

唯一性约束

确保某个属性的值在特定标签的节点中是唯一的。

-- 创建唯一性约束
CREATE CONSTRAINT person_email_unique
FOR (p:Person)
REQUIRE p.email IS UNIQUE

-- 创建带名称的约束
CREATE CONSTRAINT user_id_unique
FOR (u:User)
REQUIRE u.userId IS UNIQUE

存在性约束

确保节点或关系必须具有某个属性。

-- 节点属性存在性约束
CREATE CONSTRAINT person_name_exists
FOR (p:Person)
REQUIRE p.name IS NOT NULL

-- 关系属性存在性约束
CREATE CONSTRAINT friend_since_exists
FOR ()-[r:FRIEND]-()
REQUIRE r.since IS NOT NULL

节点键约束

组合唯一性约束,确保多个属性的组合值唯一,且这些属性必须存在。

-- 复合唯一性约束
CREATE CONSTRAINT person_name_birth_key
FOR (p:Person)
REQUIRE (p.firstName, p.lastName, p.birthDate) IS NODE KEY

关系类型约束(企业版)

限制节点可以拥有的关系类型。

-- 限制 Person 节点只能有 FRIEND 和 FAMILY 关系
CREATE CONSTRAINT person_rel_type
FOR (p:Person)
REQUIRE p IS TYPED FRIEND, FAMILY

管理约束

查看约束

-- 列出所有约束
SHOW CONSTRAINTS

-- 查看约束详情
SHOW CONSTRAINTS YIELD name, type, entityType, labelsOrTypes, properties

删除约束

-- 按名称删除约束
DROP CONSTRAINT person_email_unique

-- 如果存在则删除
DROP CONSTRAINT person_email_unique IF EXISTS

索引与约束的最佳实践

1. 索引设计原则

-- 为高基数属性创建索引
CREATE INDEX person_email FOR (p:Person) ON (p.email)

-- 为低基数属性(如性别)创建索引通常效果不佳
-- 避免:CREATE INDEX person_gender FOR (p:Person) ON (p.gender)

-- 为经常用于过滤的属性创建索引
CREATE INDEX product_category FOR (p:Product) ON (p.category)
CREATE INDEX order_date FOR (o:Order) ON (o.orderDate)

2. 约束设计原则

-- 为业务唯一标识创建唯一性约束
CREATE CONSTRAINT user_email_unique FOR (u:User) REQUIRE u.email IS UNIQUE
CREATE CONSTRAINT product_sku_unique FOR (p:Product) REQUIRE p.sku IS UNIQUE

-- 为关键业务字段创建存在性约束
CREATE CONSTRAINT user_email_required FOR (u:User) REQUIRE u.email IS NOT NULL
CREATE CONSTRAINT product_price_required FOR (p:Product) REQUIRE p.price IS NOT NULL

3. 索引维护

-- 监控索引使用情况
CALL db.stats.retrieve('INDEXES')

-- 重建索引(当索引损坏或性能下降时)
CALL db.index.full.queryNodes('person_name', '张三')

性能优化

查询优化示例

-- 未优化:全表扫描
MATCH (p:Person)
WHERE p.name = '张三'
RETURN p

-- 优化后:使用索引扫描
-- 确保已创建:CREATE INDEX person_name FOR (p:Person) ON (p.name)
MATCH (p:Person {name: '张三'})
RETURN p

索引选择

-- 范围查询使用范围索引
CREATE RANGE INDEX person_age FOR (p:Person) ON (p.age)

MATCH (p:Person)
WHERE p.age > 25 AND p.age < 35
RETURN p

-- 文本搜索使用文本索引
CREATE TEXT INDEX person_bio FOR (p:Person) ON (p.bio)

MATCH (p:Person)
WHERE p.bio STARTS WITH '软件工程师'
RETURN p

复合索引的使用

-- 创建复合索引
CREATE INDEX person_city_age FOR (p:Person) ON (p.city, p.age)

-- 有效使用:使用索引前缀
MATCH (p:Person)
WHERE p.city = '北京' AND p.age > 30
RETURN p

-- 部分有效:只使用第一个字段
MATCH (p:Person)
WHERE p.city = '北京'
RETURN p

-- 无效:跳过第一个字段
MATCH (p:Person)
WHERE p.age > 30
RETURN p

全文索引

全文索引支持复杂的文本搜索功能。

创建全文索引

-- 创建全文索引
CALL db.index.fulltext.createNodeIndex(
'productSearch',
['Product'],
['name', 'description']
)

使用全文索引

-- 基本全文搜索
CALL db.index.fulltext.queryNodes('productSearch', '笔记本电脑')
YIELD node, score
RETURN node.name, score

-- 模糊搜索
CALL db.index.fulltext.queryNodes('productSearch', '笔记本~')
YIELD node, score
RETURN node.name, score

-- 布尔搜索
CALL db.index.fulltext.queryNodes('productSearch', '笔记本 AND 游戏')
YIELD node, score
RETURN node.name, score

管理全文索引

-- 列出全文索引
CALL db.index.fulltext.listAvailableAnalyzers()

-- 删除全文索引
CALL db.index.fulltext.drop('productSearch')

实际应用示例

电商系统索引设计

-- 用户相关索引和约束
CREATE CONSTRAINT user_email_unique FOR (u:User) REQUIRE u.email IS UNIQUE
CREATE CONSTRAINT user_id_unique FOR (u:User) REQUIRE u.userId IS UNIQUE
CREATE INDEX user_name FOR (u:User) ON (u.name)
CREATE INDEX user_created_at FOR (u:User) ON (u.createdAt)

-- 商品相关索引和约束
CREATE CONSTRAINT product_sku_unique FOR (p:Product) REQUIRE p.sku IS UNIQUE
CREATE INDEX product_category FOR (p:Product) ON (p.category)
CREATE INDEX product_price FOR (p:Product) ON (p.price)
CREATE RANGE INDEX product_stock FOR (p:Product) ON (p.stockQuantity)

-- 订单相关索引
CREATE CONSTRAINT order_id_unique FOR (o:Order) REQUIRE o.orderId IS UNIQUE
CREATE INDEX order_date FOR (o:Order) ON (o.orderDate)
CREATE INDEX order_status FOR (o:Order) ON (o.status)

-- 全文索引
CALL db.index.fulltext.createNodeIndex(
'productSearch',
['Product'],
['name', 'description', 'tags']
)

社交网络索引设计

-- 用户约束
CREATE CONSTRAINT person_id_unique FOR (p:Person) REQUIRE p.personId IS UNIQUE
CREATE CONSTRAINT person_email_unique FOR (p:Person) REQUIRE p.email IS UNIQUE

-- 性能索引
CREATE INDEX person_name FOR (p:Person) ON (p.name)
CREATE INDEX person_location FOR (p:Person) ON (p.location)
CREATE RANGE INDEX person_age FOR (p:Person) ON (p.age)

-- 关系索引
CREATE INDEX friend_since FOR ()-[r:FRIEND]-() ON (r.since)
CREATE INDEX follow_created FOR ()-[r:FOLLOWS]-() ON (r.createdAt)

常见问题

1. 索引未生效

-- 检查索引是否存在
SHOW INDEXES

-- 使用 PROFILE 分析查询
PROFILE MATCH (p:Person {name: '张三'}) RETURN p

-- 确保查询条件与索引匹配
-- 索引:CREATE INDEX person_name FOR (p:Person) ON (p.name)
-- 有效:MATCH (p:Person {name: '张三'})
-- 无效:MATCH (p:Person) WHERE toLower(p.name) = '张三'

2. 约束冲突

-- 处理唯一性约束冲突
MATCH (p:Person {email: '[email protected]'})
SET p.email = '[email protected]'
-- 如果 [email protected] 已存在,会报错

-- 解决方案:先检查是否存在
MATCH (existing:Person {email: '[email protected]'})
WITH count(existing) AS count
WHERE count = 0
MATCH (p:Person {email: '[email protected]'})
SET p.email = '[email protected]'

3. 索引创建失败

-- 检查是否有重复数据
MATCH (p:Person)
WITH p.email AS email, count(*) AS count
WHERE count > 1
RETURN email, count

-- 清理重复数据后再创建约束

下一步

了解了索引和约束后,继续学习 数据建模,掌握如何设计高效的图数据模型。