Django中执行update操作时,如何跳过锁定行?
Django中select_for_update(skip_locked=True)结合update的正确实现
非原生SQL的Django ORM方案
可以用Subquery和OuterRef构造单次查询,既不用两次请求数据库,也不会把大量ID加载到内存里:
from django.db.models import Subquery, OuterRef # 子查询:获取未锁定的行ID并加锁 locked_ids = MyModel.objects.filter(id=OuterRef('id')).select_for_update(skip_locked=True).values('id') # 仅更新子查询返回的未锁定行 MyModel.objects.filter(id__in=Subquery(locked_ids)).update(some_field='whatever')
生成的SQL示例:
UPDATE "my_model" SET "some_field" = 'whatever' WHERE "my_model"."id" IN ( SELECT U0."id" FROM "my_model" U0 WHERE U0."id" = ("my_model"."id") FOR UPDATE SKIP LOCKED )
这个方案的优势:
- 单次数据库查询,由数据库内部处理子查询,避免应用层内存压力
- 严格遵循
skip_locked=True规则,只更新未被锁定的行
注意:需要数据库支持FOR UPDATE SKIP LOCKED(如PostgreSQL 9.5+、MySQL 8.0.1+),这和select_for_update(skip_locked=True)本身的要求一致。
原生SQL方案
直接编写包含FOR UPDATE SKIP LOCKED的UPDATE语句,不同数据库语法略有区别:
PostgreSQL
UPDATE my_model SET some_field = 'whatever' WHERE id IN ( SELECT id FROM my_model FOR UPDATE SKIP LOCKED );
MySQL 8.0.1+
MySQL不允许在UPDATE的WHERE子查询直接引用被更新表,需要嵌套临时子查询:
UPDATE my_model SET some_field = 'whatever' WHERE id IN ( SELECT id FROM ( SELECT id FROM my_model FOR UPDATE SKIP LOCKED ) AS temp );
在Django中执行原生SQL:
from django.db import connection with connection.cursor() as cursor: # 以PostgreSQL为例,参数用%s占位符防止SQL注入 cursor.execute(""" UPDATE my_model SET some_field = %s WHERE id IN ( SELECT id FROM my_model FOR UPDATE SKIP LOCKED ); """, ['whatever'])
内容的提问来源于stack exchange,提问作者Troy
相关产品推荐
相关产品推荐

