Python2.7+Django1.9.2中,大QuerySet的if判断为何远慢于exists()?
Why is checking
if records: slow for a large Django QuerySet, but records.exists() fast? 你的猜测完全正确!这背后的核心原因在于Django QuerySet的惰性求值机制,以及两种判断方式触发的截然不同的数据库操作:
1. 为什么if records:会慢到离谱?
在你使用的Django 1.9版本中,当用if records:判断QuerySet是否为空时,会触发QuerySet的__nonzero__()方法(Python 2的布尔判断逻辑),而这个方法最终会调用__len__()——也就是计算QuerySet的总长度。
计算长度意味着什么?Django会执行完整的SQL查询,把所有匹配的56000条记录全部加载到内存,并转换成对应的Record模型实例,之后才会返回结果数量。这个过程包含:
- 数据库执行全量查询并返回所有数据
- Django把数据库返回的原始数据序列化生成模型对象
- 把这些对象存储到QuerySet的内部缓存中
这一系列操作的IO和内存开销都极大,所以才会耗时近5秒。对应的SQL大致是这样的:
SELECT * FROM record_record WHERE result = 0;
2. 为什么records.exists()能做到毫秒级响应?
exists()是Django专门为“判断QuerySet是否有匹配项”这个场景设计的优化方法。它不会加载任何数据到内存,而是直接让数据库执行一个高效的EXISTS查询:
SELECT EXISTS(SELECT 1 FROM record_record WHERE result = 0);
数据库执行这个查询时,只需要找到第一条匹配的记录就会立即返回True,不需要遍历所有56000条数据,也不需要把任何数据返回给应用层。整个过程完全在数据库层面完成,开销极小,所以速度快到毫秒级。
而且exists()不会触发QuerySet的缓存,也不会生成任何模型实例,纯粹是做存在性判断。
额外小贴士
如果你之后还需要使用records中的数据,需要注意:
- 先用
exists()判断,之后再访问records依然会触发全量查询(因为QuerySet是惰性的),不过查询结果会被缓存,第二次访问就会快很多 - 如果只是需要判断存在性,不需要后续处理数据,
exists()绝对是最优选择;如果后续要处理数据,也建议先用exists()判断,避免空QuerySet的无效处理
内容的提问来源于stack exchange,提问作者Dean Christian Armada
相关产品推荐
相关产品推荐

