Django QuerySet如何高效获取列表中未匹配数据库记录的元素
Django 批量判断列表元素是否存在对应数据库记录的实现
常规实现方案
假设存在Django模型Data,其包含一个名为hash的CharField字段,另有一个存储哈希值的列表all_hashes。
我们通常使用__in语法批量过滤匹配的模型对象,示例代码如下:
all_hashes = ['45df...','ab23...', ... ] filtered_data = Data.objects.filter(hash__in=all_hashes)
提取所有匹配成功的哈希值:
filtered_hashes = [obj.hash for obj in filtered_data]
未匹配到对应数据库记录的哈希值,可以直接通过集合差集运算得到:
new_hashes = set(all_hashes) - set(filtered_hashes)
注意:绝对不要逐一遍历
all_hashes列表,单独查询数据库判断每个值是否存在匹配对象。这种写法会产生N次数据库查询,也就是常说的N+1性能问题,数据量稍大就会导致接口响应缓慢,应当完全避免。
有开发者提出疑问:是否存在更直接的方式,不需要手动做集合运算,就能识别列表中无对应匹配数据库记录的项?例如类似如下假想API,直接返回对应布尔匹配结果列表:
# 期望返回结果:[True, False, ...],和传入的all_hashes顺序一一对应 objects_existing = Data.objects.exist(hash__in=all_hashes)
对应布尔结果列表的实现方式
Django ORM没有内置上述直接返回顺序对应布尔列表的API,但只需要2步就能实现同等效果,全程仅触发1次数据库查询,性能和原生__in查询完全一致:
- 单次查询拉取所有存在的hash值,存入集合(集合的成员判断时间复杂度为O(1),性能远高于列表)
# 用values_list只取hash字段,避免拉取多余字段数据,进一步提升查询效率 existing_hash_set = set( Data.objects.filter(hash__in=all_hashes).values_list('hash', flat=True) )
- 按照原列表顺序逐个判断元素是否在已存在集合中,生成对应布尔列表
objects_existing = [hash_val in existing_hash_set for hash_val in all_hashes]
最终得到的objects_existing就是和all_hashes长度完全一致、顺序一一对应的布尔列表,位置为True代表对应hash存在数据库记录,False代表不存在。
这个方案的优势非常明显:
- 仅触发1次数据库查询,无N+1性能问题
- 结果顺序和传入的原列表完全对齐,不会因为集合无序丢失对应关系
- 内存运算开销极低,哪怕传入的列表长度达到数万级别,也不会有明显性能延迟
- 如果
hash字段建立了数据库索引,整个查询的耗时会进一步降低,完全可以覆盖绝大多数业务场景需求
内容的提问来源于stack exchange,提问作者ecp
相关产品推荐
相关产品推荐

