You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 15:15:17