索引与约束
索引和约束是数据库性能优化和数据完整性保障的重要手段。本章介绍 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
-- 清理重复数据后再创建约束
下一步
了解了索引和约束后,继续学习 数据建模,掌握如何设计高效的图数据模型。