Neomodel如何高效同时查询关联节点及对应关系模型数据
neomodel 单次查询同时获取关联节点与对应关系模型的方法
问题场景
参考neomodel官方文档定义如下数据模型:
from neomodel import StructuredRel, StructuredNode, StringProperty, DateTimeProperty, RelationshipTo import datetime import pytz class FriendRel(StructuredRel): since = DateTimeProperty( default=lambda: datetime.datetime.now(pytz.utc) ) met = StringProperty() class Person(StructuredNode): name = StringProperty() friends = RelationshipTo('Person', 'FRIEND', model=FriendRel)
基于上述模型创建测试数据:
bob = Person(name='bob').save() frank = Person(name='frank').save() rel = bob.friends.connect(frank, {'since': datetime.datetime.now(), 'met': 'Germany'})
需求为单次数据库查询同时获取某个人物对象的所有好友节点,以及好友关系对应的FriendRel关系对象,避免常规写法产生的N+1查询问题。
常规的官方示例写法和循环写法存在效率缺陷:
- 先取关联节点、再单独调用
relationship()取关系的方式,会产生多次数据库查询 - 循环中为每个好友单独调用
relationship()方法的写法,会产生典型的N+1查询问题,好友数量越多查询性能越差
原生Cypher本身支持单条语句同时返回起始节点、关系、目标节点:
MATCH(i:Person{name: 'bob'})-[j:FRIEND]->(k) RETURN i,j,k
官方支持的实现方案
neomodel提供了两种原生ORM层面的方案,无需手写原生Cypher即可实现单查询拉取所有关联节点和关系,完全避免N+1问题。
方案1:直接拉取所有关联关系实例
关系管理器自带all_relationships()方法,执行单条Cypher即可拉取指定方向的所有关联关系实例,每个关系实例可直接访问属性,同时通过内置方法拿到两端的节点对象,全程无额外查询。
示例代码:
bob = Person.nodes.get(name='bob') # 仅执行1次数据库查询,拉取所有FRIEND关系及两端节点数据 friend_rels = bob.friends.all_relationships() for rel in friend_rels: # rel 为 FriendRel 类的实例,可直接访问关系属性 meet_place = rel.met meet_time = rel.since # 对于RelationshipTo定义的关联,end_node()返回目标好友节点,为Person类实例 friend = rel.end_node() print(f"好友{friend.name},相识于{meet_place},相识时间{meet_time}")
方案2:预加载关联数据
查询起始节点时使用prefetch_relations()方法指定要预加载的关联字段,neomodel会在单次查询中把关联的节点、关系全部拉取到缓存中,后续遍历关联节点、调用relationship()方法时会直接读取缓存,不会触发额外数据库查询。
示例代码:
# 查询bob节点时同步预加载friends关联的所有节点和关系,仅1次数据库查询 bob = Person.nodes.filter(name='bob').prefetch_relations('friends').get() # 后续遍历、获取关系均走本地缓存,无额外DB开销 for friend in bob.friends: rel = bob.friends.relationship(friend) print(friend.name, rel.met, rel.since)
补充:原生Cypher配合自动实例化
如果需要自定义更复杂的查询逻辑,可使用neomodel内置的数据库客户端执行Cypher,开启resolve_objects=True参数后,返回结果会自动映射为你定义的StructuredNode和StructuredRel实例,同样仅需一次查询:
from neomodel import db query = """ MATCH (bob:Person {name: 'bob'})-[rel:FRIEND]->(friend:Person) RETURN rel, friend """ # resolve_objects=True 开启自动对象映射 results, _ = db.cypher_query(query, resolve_objects=True) for rel, friend in results: # rel为FriendRel实例,friend为Person实例,可直接使用 print(friend.name, rel.met)
内容的提问来源于stack exchange,提问作者Ethan Posner
相关产品推荐
相关产品推荐

