如何以并发安全方式删除Django中quantity为0的Stock行?
问题分析
你现在用的Stock.objects.filter(product=product, quantity=0).delete()会被Django优化成两步操作:先查出符合条件的Stock记录ID,再执行DELETE WHERE id IN (...)。这会带来并发安全问题——在查ID到删数据的间隙里,其他进程可能把这些记录的quantity改成非0值,导致误删不该删的库存。
解决方案
方案1:用_raw_delete执行单条DELETE语句
Django的QuerySet有个内部方法_raw_delete,它会直接生成并执行单条DELETE FROM ... WHERE ...语句,跳过默认的ID查询和级联/信号处理,确保删除操作是原子性的。
示例代码:
from django.db import connection # 数据库连接别名,默认用'default'即可 db_alias = 'default' # 构造过滤条件的QuerySet queryset = Stock.objects.filter(product=product, quantity=0) # 执行原生删除 queryset._raw_delete(using=db_alias)
注意:
_raw_delete是Django的内部API,虽然实际项目里常用,但版本升级时可能有变动。另外,这个方法不会触发pre_delete/post_delete信号,也不处理外键的级联删除逻辑,如果你的业务依赖这些特性,得额外处理。
方案2:原子事务+select_for_update(适合需要触发信号/级联删除的场景)
如果你的业务必须触发删除信号或者处理外键级联删除,可以用atomic事务包裹,再通过select_for_update锁定符合条件的行,防止并发修改:
示例代码:
from django.db import transaction with transaction.atomic(): # 锁定符合条件的行,其他进程在事务结束前没法修改这些行 stock_to_delete = Stock.objects.select_for_update().filter(product=product, quantity=0) stock_to_delete.delete()
这个方案靠数据库行锁保证了从查询到删除的过程中,目标Stock记录不会被改,避免了并发问题。但缺点是会占用数据库锁资源,高并发场景下可能影响性能。
方案3:直接执行原生SQL(最稳妥的方式)
如果上面的ORM方案满足不了需求,直接写原生SQL是最靠谱的,能完全控制删除逻辑:
示例代码:
from django.db import connection with connection.cursor() as cursor: cursor.execute( "DELETE FROM app_stock WHERE product_id = %s AND quantity = 0", [product.id] )
这种方式直接生成你想要的单条DELETE语句,利用数据库的原子性保证并发安全,还能灵活适配复杂业务逻辑。
内容的提问来源于stack exchange,提问作者Andrew Veitch

