Django实现按用户隔离的Serial与Bag字段联动自增方案咨询
Django 实现用户维度独立的Serial/Bag联动自增编号
核心逻辑是按用户隔离计数序列,通过数据库行级排他锁保证并发场景下编号不重复,具体实现步骤如下:
1. 模型设计
不要依赖数据库全局自增字段,单独维护每个用户的计数游标,避免全表扫描查询最新编号,同时给业务表加联合索引提升查询效率:
from django.db import models from django.contrib.auth import get_user_model User = get_user_model() # 业务存储表 class BusinessRecord(models.Model): operator = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="操作用户") bag = models.PositiveIntegerField(verbose_name="Bag编号") serial = models.PositiveIntegerField(verbose_name="Serial编号") # 其余业务字段按需添加 create_time = models.DateTimeField(auto_now_add=True) class Meta: indexes = [ models.Index(fields=["operator", "-bag", "-serial"]), ] # 用户计数游标表,每个用户仅存一条记录,维护当前最新的编号状态 class UserNumberCursor(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) current_bag = models.PositiveIntegerField(default=1) current_serial = models.PositiveIntegerField(default=0)
2. 编号生成核心逻辑
所有编号生成操作必须放在数据库事务内,查询游标时加行级排他锁,避免并发请求拿到重复编号。进位规则严格匹配需求:每个Bag对应100个Serial,Serial到100后下一条自动重置Serial为1、Bag自增1。
from django.db import transaction from .models import BusinessRecord, UserNumberCursor def get_next_number_pair(user): with transaction.atomic(): # 加锁查询用户游标,不存在则初始化,事务结束前其他请求无法修改该用户的游标数据 cursor, _ = UserNumberCursor.objects.select_for_update().get_or_create( user=user, defaults={"current_bag": 1, "current_serial": 0} ) # 序号递增 next_serial = cursor.current_serial + 1 next_bag = cursor.current_bag # 满100进位 if next_serial > 100: next_bag += 1 next_serial = 1 # 更新游标状态 cursor.current_bag = next_bag cursor.current_serial = next_serial cursor.save() return next_bag, next_serial
3. 业务调用
在创建/更新记录需要生成编号的位置,直接调用上述函数即可,注意如果是更新场景,要先判断当前记录是否已经分配过编号,避免重复生成覆盖原有值:
# 示例:视图中创建记录时生成编号 from django.http import JsonResponse def create_record(request): # 提前完成登录校验,确保request.user为当前登录的操作用户 bag, serial = get_next_number_pair(request.user) record = BusinessRecord.objects.create( operator=request.user, bag=bag, serial=serial, # 其余业务字段赋值 ) return JsonResponse({"record_id": record.id, "bag": bag, "serial": serial})
注意事项
select_for_update行锁必须在事务内生效,不要拆分逻辑到事务外执行,否则会出现并发下编号重复、错配的问题- 若单Bag容纳的Serial数量需要调整,直接修改判断逻辑中的100为配置值即可,无需改动其他逻辑
- 不推荐通过直接查询用户最新业务记录计算编号的方案,数据量增长后查询性能差,且长事务下容易出现锁表现象
内容的提问来源于stack exchange,提问作者Islam Fahmy
相关产品推荐
相关产品推荐

