Django使用prefetch_related解决N+1查询问题的实操咨询
N+1查询问题原理与优化方案
原代码的问题
你当前的写法触发了典型的N+1查询问题,总查询次数等于ProductSpy的数量加1:
- 第1次查询:
ProductSpy.objects.all()执行1次SQL,拉取所有ProductSpy的基础数据 - 第N次查询:循环中每次访问
productSpy.product时,Django会为每个ProductSpy单独发起1次SQL查询对应的Product数据,若有N条ProductSpy记录就会产生N次额外查询,数据量越大性能损耗越严重。
关联查询优化逻辑说明
Django提供了两种提前加载关联数据的方案:
select_related:通过SQL JOIN的方式,将关联表的字段直接拼接在主表的查询结果中,仅执行1次SQL,仅支持正向外键、一对一关联场景prefetch_related:分两次查询,第一次拉取主表数据,第二次通过IN语句一次性拉取所有关联的外键数据,再在Python层完成数据关联,支持正向/反向外键、多对多关联场景
你的场景属于ProductSpy正向关联Product的外键场景,两种方案都可以使用,优先选select_related性能更好。
优化代码
方案1:使用select_related(推荐)
# Get the amount of kilo attached to products product_data = {} # 提前加载关联的product对象,全程仅执行1次SQL for productSpy in ProductSpy.objects.select_related('product').all(): product_data[productSpy.product.product_id] = productSpy.kilo
方案2:使用prefetch_related
# Get the amount of kilo attached to products product_data = {} # 提前预取关联的product对象,全程执行2次SQL,避免N+1问题 for productSpy in ProductSpy.objects.prefetch_related('product').all(): product_data[productSpy.product.product_id] = productSpy.kilo
方案3:最高性能写法(无需实例化ORM对象)
如果仅需要product_id和kilo两个字段,可直接通过values_list查询所需字段,省略ORM对象实例化的开销,仅执行1次SQL:
product_data = dict(ProductSpy.objects.values_list('product__product_id', 'kilo'))
内容的提问来源于stack exchange,提问作者Jannickla
相关产品推荐
相关产品推荐

