多对多关联下批量更新指定店铺客户订阅字段的正确方式
问题分析与解决方案
模型定义
class Customer(models.Model): stores = models.ManyToManyField(Store, related_name="customers") subscriptions = models.ManyToManyField(Subscription, through="CustomerSubscription") class CustomerSubscription(models.Model): subscription = models.ForeignKey(Subscription) customer = models.ForeignKey(Customer) class Subscription(models.Model): is_valid = models.BooleanField()
给定store = Store.objects.get(pk=1),需要将该店铺所有客户的全部订阅的is_valid设为False。
现有方式的问题
第一种循环客户的方式
customers = store.customers.all() for c in customers: c.subscriptions.update(is_valid=False)这种方式会产生 N+1次SQL查询:1次查询获取所有客户,每个客户对应1次update查询。客户数量越多,查询次数越多,效率越低。
使用prefetch_related的方式
customers = store.customers.prefetch_related("subscriptions") for c in customers: c.subscriptions.update(is_valid=False)这里
prefetch_related完全是多余的——c.subscriptions.update()是直接向数据库发送批量更新的SQL语句,不会用到预取到内存中的订阅对象。反而会多1次预取所有订阅的查询,总查询次数变成 N+2次,比第一种方式更多。
最优实现方式
直接利用Django ORM的关联查询能力,一次性过滤出目标订阅并批量更新,只需要1次SQL查询:
# 通过Subscription反向关联Customer,再关联到Store Subscription.objects.filter( customer_set__stores=store ).update(is_valid=False)
或者通过中间表CustomerSubscription间接过滤(效果相同):
Subscription.objects.filter( customersubscription__customer__stores=store ).update(is_valid=False)
核心原理
- 利用ORM的关联过滤逻辑,直接定位到所有属于该店铺客户的订阅记录
update()方法会生成一条批量更新的SQL语句,一次性修改所有符合条件的记录,彻底避免循环带来的多次查询开销
内容的提问来源于stack exchange,提问作者Baz
相关产品推荐
相关产品推荐

