MongoEngine中no_dereference()性能疑问及使用规范咨询
问题与解答
环境信息
Environment: python: 3.8 mongodb: 3.4 (on docker container) mongoengine: 0.20.0
问题背景
我的MongoDB中有一个DEVICE集合,已填充104个文档,该集合包含9个ReferenceField属性,关联字段对应的文档总大小远大于单个DEVICE文档本身。
我对以下两个查询的性能进行了测试:
Device.objects(name=name).first()Device.objects(name=name).no_dereference().first()
无论测试是在MongoDB主机本地还是远程主机进行,第一个语句的性能都远优于第二个语句。
我想了解:
- 为何第二个语句性能远差于第一个?
no_dereference()方法实际执行了什么操作?- 使用及不使用该方法的通用准则是什么?
解答
1. 为什么no_dereference()性能更差?
mongoengine默认行为是:查询带有ReferenceField的文档时,不会自动解引用——返回的ReferenceField仅为存储的ObjectId,不会去关联集合拉取完整文档。而你使用的0.20.0版本中,no_dereference()反而会触发额外开销:它会强制将每个ReferenceField的值从ObjectId转换为DBRef对象(包含集合名和ObjectId的封装对象)。
你的场景里有9个ReferenceField,每个都要完成DBRef的构造,这比直接返回ObjectId多了一层对象创建和属性赋值的开销。虽然no_dereference()不会拉取关联文档,但这部分转换的累积耗时,让它比默认查询更慢。
而第一个查询直接返回带有ObjectId的Device实例,没有额外的DBRef转换步骤,所以性能更优。
2. no_dereference()实际执行的操作
在mongoengine 0.20.0版本中,该方法的核心逻辑是:
- 修改查询集的
_dereference标记为False,强制禁止自动解引用(但默认已经是禁止状态)。 - 在查询结果序列化时,将每个
ReferenceField的ObjectId值转换为DBRef对象,而非默认的ObjectId或延迟加载代理。 - 不会触发任何关联集合的查询操作,仅改变
ReferenceField的返回格式。
3. 使用及不使用该方法的通用准则
不使用no_dereference()(默认行为)的场景:
- 仅需访问
ReferenceField的ObjectId,不需要关联文档数据:默认返回的ObjectId轻量,性能最优。 - 后续可能需要手动解引用特定字段:默认的
ObjectId或代理对象更方便通过fetch()方法拉取关联文档。 - 追求查询性能,避免不必要的对象转换开销。
使用no_dereference()的场景:
- 需要明确获取
DBRef格式的数据:比如要输出符合DBRef规范的序列化结果,或与依赖DBRef的系统交互。 - 在特殊查询场景下,需显式声明禁止解引用(尽管默认已禁止,但某些复杂查询链中可用来明确语义)。
关键注意点:
- 若只是想避免拉取关联文档,完全不需要调用
no_dereference()——默认行为已经是不自动解引用,调用它反而会增加不必要的转换开销。 - 新版mongoengine(0.24+)中该方法的行为已调整,更偏向于禁止延迟加载,但你的0.20.0版本需遵循旧版逻辑。
内容的提问来源于stack exchange,提问作者H.Sheng
相关产品推荐
相关产品推荐

