Django 使用Max聚合与F表达式避免生成增量字段时的竞态条件
可行实现方案(按适用场景优先级排序)
方案1:事务+行级锁(全数据库兼容,实现最简单)
核心原理是通过select_for_update给查询加排他锁,确保同一时间只有一个事务能拿到最大值,彻底避免竞态:
from django.db import transaction from django.db.models import Max @transaction.atomic def create_item(**kwargs): # select_for_update会对查询涉及的所有行加排他锁,其他同类查询会阻塞直到当前事务提交 max_inc = Item.objects.select_for_update().aggregate(res=Max("increment"))["res"] or 0 item = Item.objects.create( increment=max_inc + 1, **kwargs ) return item
适用场景:并发量不高、表数据量不大的场景,实现成本最低。
方案2:单条原子INSERT语句(无锁,性能更高)
将最大值计算和插入合并为单条SQL,利用数据库单条SQL的原子性避免竞态,不需要额外加锁,性能比方案1高:
from django.db.models import Max, Value, IntegerField from django.db.models.functions import Coalesce Item.objects.create( # 子查询计算下一个序号,整个操作为原子操作,无竞态问题 increment=Item.objects.annotate( next_val=Coalesce(Max("increment") + 1, Value(1, output_field=IntegerField())) ).values("next_val")[:1] )
适用场景:中高并发、表数据量适中的场景,不需要额外维护其他表。
方案3:独立计数器表(高并发大表最优方案)
如果Item表数据量极大,每次查询全表最大值性能过低,可以单独维护一张计数器表,锁粒度极小,并发性能最高:
# 新增计数器模型 class IncrementCounter(models.Model): current_val = models.PositiveIntegerField(default=0) @transaction.atomic def create_item(**kwargs): # 仅锁定计数器表的单行,不影响Item表的任何操作 counter = IncrementCounter.objects.select_for_update().first() counter.current_val += 1 counter.save(update_fields=["current_val"]) return Item.objects.create( increment=counter.current_val, **kwargs )
前置准备:使用前手动给IncrementCounter表插入一条初始数据,current_val设为0即可。
额外优化建议
给Item的increment字段加上唯一约束,作为极端场景下的最后兜底,避免任何异常情况出现重复值:
increment = models.PositiveIntegerField(_('Increment'), null=True, blank=True, default=None, unique=True)
内容的提问来源于stack exchange,提问作者Hal
相关产品推荐
相关产品推荐

