【问题标题】:Django/Postgres: Returning error: duplicate key value violates unique constraintDjango/Postgres:返回错误:重复键值违反唯一约束
【发布时间】:2018-08-16 03:20:08
【问题描述】:

我一直在从事的一个项目中多次出现此问题,虽然我使用了我在此站点上找到的“python manage.py sqlsequencereset”修复程序,但它可以工作一段时间,并且然后再次开始抛出错误。

我不断遇到的错误非常简单:

IntegrityError at /projects/v/portola/planting-sites/new/
duplicate key value violates unique constraint "urbanforest_plantingsite_pkey"
DETAIL:  Key (id)=(194016) already exists.

在我看来,我有:

form = PlantingSiteForm(request.POST)
if form.is_valid():
    f = form.save(commit=False)
    if PlantingSite.objects.all().exists():
        last_planting_site = PlantingSite.objects.all().order_by('-created_at')[0]
        f.id_planting_site = last_planting_site.id_planting_site
        f.id_planting_site += 1
    else:
        f.id_planting_site = 100000
    if request.POST.get('neighborhood_id'):
        f.neighborhood = Neighborhood.objects.filter(id_neighborhood=request.POST.get('neighborhood_id'))[0]
    if request.POST.get('district_id'):
        f.district = District.objects.filter(id_district=request.POST.get('district_id'))[0]
    if request.POST.get('basin_type_id'):
        f.basin_type = BasinType.objects.filter(id_basin_type=request.POST.get('basin_type_id'))[0]
    if request.POST.get('aspect_id'):
        f.aspect = Aspect.objects.filter(id_aspect=request.POST.get('aspect_id'))[0]
    if request.POST.get('orientation_id'):
        f.orientation = Orientation.objects.filter(id_orientation=request.POST.get('orientation_id'))[0]
    if request.POST.get('hardscape_damage_id'):
        f.hardscape_damage = HardscapeDamage.objects.filter(id_hardscape_damage=request.POST.get('hardscape_damage_id'))[0]
    if request.POST.get('status_id'):
        f.status = PlantingSiteStatus.objects.filter(id_planting_site_status=request.POST.get('status_id'))[0]
    else:
        f.status = PlantingSiteStatus.objects.filter(id_planting_site_status=9)[0]
    if f.zipcode:
        f.property_zipcode = f.zipcode
    f.created_by_fuf = True
    f.created_by = request.user 
    f.save()

我现在已经执行了几次 sqlsequencereset,但几周后错误又回来了。我没有导入任何数据,但我的同事正在现场使用他们的手机创建新对象,一次一个。

运行 sqlsequencereset 得到这个代码:

SELECT setval(pg_get_serial_sequence('"urbanforest_plantingsite"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "urbanforest_plantingsite";

我应该运行一些更具体的东西吗?我对 Postgres 不是很熟悉,因为我刚刚使用 Django 命令来处理所有数据库调用。

编辑:要添加的一件事,我发现运行 sqlsequencereset 会生成用于调整 sql 调用的代码,它实际上并没有运行它。所以我跑了:
python manage.py sqlsequencereset urbanforest | python manage.py dbshel​​l 它注入了代码并重建了表。

不幸的是,几分钟后我收到了同样的错误。

【问题讨论】:

  • 你有竞争条件;如果两个人同时尝试创建PlantingSite,他们会得到相同的 ID。为什么要手动增加 ID?此外,仅仅看一眼代码,您正在做的很多事情似乎都可以通过表单的save 来完成。
  • 只有两个人在现场使用该应用程序,而且他们很少同时在那里,所以希望现在比赛条件不是问题,我不知道该怎么办做以解决这个长期问题。对于递增的 ID,我只想要一个不直接连接到 PK 的面向公众的 ID,以防万一。
  • 如果order_by('-created_at')返回的对象不一定是ID最高的对象,则可能不是竞争条件。除非您有充分的理由使用手动 ID,否则它只会徒劳无功。您可以使用F 表达式或select_for_update 来考虑并发性,并以不同的方式进行查询以确保它确实获得最高ID,但这似乎是多余的。

标签: django postgresql


【解决方案1】:

所以我通过将网站创建移到 while 循环中来解决这个问题(至少现在是这样)。我认为问题在于 Django 如何使用模型表单来处理创建对象。由于您创建对象并使用 form.save(commit=False) 将其保存到内存中,因此您可能会遇到问题,因为它会在将对象提交到数据库之前将这些其他字段添加到对象中。

所以,至少现在,这已经解决了问题:

while True:
        try:    
            form = PlantingSiteForm(request.POST)
                if form.is_valid():
                    f = form.save(commit=False)
                    if PlantingSite.objects.all().exists():
                        last_planting_site = PlantingSite.objects.all().order_by('-created_at')[0]
                        f.id_planting_site = last_planting_site.id_planting_site
                        f.id_planting_site += 1
                    else:
                        f.id_planting_site = 100000
                    if request.POST.get('neighborhood_id'):
                        f.neighborhood = Neighborhood.objects.filter(id_neighborhood=request.POST.get('neighborhood_id'))[0]
                    if request.POST.get('district_id'):
                        f.district = District.objects.filter(id_district=request.POST.get('district_id'))[0]
                    if request.POST.get('basin_type_id'):
                        f.basin_type = BasinType.objects.filter(id_basin_type=request.POST.get('basin_type_id'))[0]
                    if request.POST.get('aspect_id'):
                        f.aspect = Aspect.objects.filter(id_aspect=request.POST.get('aspect_id'))[0]
                    if request.POST.get('orientation_id'):
                        f.orientation = Orientation.objects.filter(id_orientation=request.POST.get('orientation_id'))[0]
                    if request.POST.get('hardscape_damage_id'):
                        f.hardscape_damage = HardscapeDamage.objects.filter(id_hardscape_damage=request.POST.get('hardscape_damage_id'))[0]
                    if request.POST.get('status_id'):
                        f.status = PlantingSiteStatus.objects.filter(id_planting_site_status=request.POST.get('status_id'))[0]
                    else:
                        f.status = PlantingSiteStatus.objects.filter(id_planting_site_status=9)[0]
                    if f.zipcode:
                        f.property_zipcode = f.zipcode
                    f.created_by_fuf = True
                    f.created_by = request.user 
                    f.save()
        except IntegrityError:
            sleep(random.uniform(0.001, 0.5)) # chill out, try again
            messages.success(request, "Still creating previous site, trying again...")
            continue

所以它会尝试创建对象,如果由于完整性错误而失败,它会休眠一秒钟,然后再试一次。到目前为止,非常好,我的同事正在现场使用它,我还没有看到任何错误。

【讨论】:

    猜你喜欢
    • 2012-03-03
    • 2012-06-20
    • 2018-08-26
    • 2021-10-23
    • 2019-03-20
    • 2021-04-10
    • 2020-05-28
    • 2016-06-12
    相关资源
    最近更新 更多