【问题标题】:django.db.utils.IntegrityError: duplicate key value violates unique constraint "spirit_category_category_pkey"django.db.utils.IntegrityError:重复键值违反唯一约束“spirit_category_category_pkey”
【发布时间】:2016-04-14 05:09:15
【问题描述】:

错误

我正在使用 django 和 spirit 建立一个网站。在测试中,当我将新数据插入到名为spirit_category_category 的表中时,出现以下错误:

django.db.utils.IntegrityError: duplicate key value violates unique constraint "spirit_category_category_pkey"
DETAIL:  Key (id)=(1) already exists.

请注意,表中已经有另外两条 ID 分别为 12 的记录。所以插入Key(id)=(1) 当然是行不通的。但是执行的 sql 不包括 id 字段。也就是Key (id)=(1)是postgresql自动生成的,为什么会生成一个已经存在的id呢?

原因

为了找出原因,我在 postgresql 中运行了以下命令:

test_spiritdb=# select start_value, last_value, max_value from spirit_category_category_id_seq;
 start_value | last_value |      max_value      
-------------+------------+---------------------
           1 |          1 | 9223372036854775807
(1 row)

所以基本上last_value就是1,所以postgresql每次都会生成Key (id)=(1),我试着把它改成3,一切都很好。

test_spiritdb=# alter sequence spirit_category_category_id_seq restart with 3;

我不知道如何修复它以进行测试

测试通过了。但它是一个测试,所以改变一个测试表是没有意义的,因为每次测试都会删除并重新创建测试数据库,所以下次测试会再次失败,因为last_value仍然会生成为1 .所以我想知道为什么django/postgresql 会为last_value 生成这样一个异常值?如何解决? category 的模型和迁移如下,如果有帮助的话。

models.py

# -*- coding: utf-8 -*-

from __future__ import unicode_literals

from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
from django.conf import settings

from .managers import CategoryQuerySet
from ..core.utils.models import AutoSlugField

class Category(models.Model):

    parent = models.ForeignKey('self', verbose_name=_("category parent"), null=True, blank=True)

    title = models.CharField(_("title"), max_length=75)
    slug = AutoSlugField(populate_from="title", db_index=False, blank=True)
    description = models.CharField(_("description"), max_length=255, blank=True)
    is_global = models.BooleanField(_("global"), default=True,
                                    help_text=_('Designates whether the topics will be'
                                                'displayed in the all-categories list.'))
    is_closed = models.BooleanField(_("closed"), default=False)
    is_removed = models.BooleanField(_("removed"), default=False)
    is_private = models.BooleanField(_("private"), default=False)

    # topic_count = models.PositiveIntegerField(_("topic count"), default=0)

    objects = CategoryQuerySet.as_manager()

    class Meta:
        ordering = ['title', 'pk']
        verbose_name = _("category")
        verbose_name_plural = _("categories")

    def get_absolute_url(self):
        if self.pk == settings.ST_TOPIC_PRIVATE_CATEGORY_PK:
            return reverse('spirit:topic:private:index')
        else:
            return reverse('spirit:category:detail', kwargs={'pk': str(self.id), 'slug': self.slug})

    @property
    def is_subcategory(self):
        if self.parent_id:
            return True
        else:
            return False

0001_initial.py

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
import spirit.core.utils.models


class Migration(migrations.Migration):

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Category',
            fields=[
                ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=True, auto_created=True)),
                ('title', models.CharField(verbose_name='title', max_length=75)),
                ('slug', spirit.core.utils.models.AutoSlugField(db_index=False, populate_from='title', blank=True)),
                ('description', models.CharField(verbose_name='description', max_length=255, blank=True)),
                ('is_closed', models.BooleanField(verbose_name='closed', default=False)),
                ('is_removed', models.BooleanField(verbose_name='removed', default=False)),
                ('is_private', models.BooleanField(verbose_name='private', default=False)),
                ('parent', models.ForeignKey(null=True, verbose_name='category parent', to='spirit_category.Category', blank=True)),
            ],
            options={
                'ordering': ['title', 'pk'],
                'verbose_name': 'category',
                'verbose_name_plural': 'categories',
            },
        ),
    ]

0002_auto_20150728_0442.py

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
from django.conf import settings


def default_categories(apps, schema_editor):
    Category = apps.get_model("spirit_category", "Category")

    if not Category.objects.filter(pk=settings.ST_TOPIC_PRIVATE_CATEGORY_PK).exists():
        Category.objects.create(
            pk=settings.ST_TOPIC_PRIVATE_CATEGORY_PK,
            title="Private",
            slug="private",
            is_private=True
        )

    if not Category.objects.filter(pk=settings.ST_UNCATEGORIZED_CATEGORY_PK).exists():
        Category.objects.create(
            pk=settings.ST_UNCATEGORIZED_CATEGORY_PK,
            title="Uncategorized",
            slug="uncategorized"
        )


class Migration(migrations.Migration):

    dependencies = [
        ('spirit_category', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(default_categories),
    ]

0003_category_is_global.py

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations


class Migration(migrations.Migration):

    dependencies = [
        ('spirit_category', '0002_auto_20150728_0442'),
    ]

    operations = [
        migrations.AddField(
            model_name='category',
            name='is_global',
            field=models.BooleanField(default=True, help_text='Designates whether the topics will bedisplayed in the all-categories list.', verbose_name='global'),
        ),
    ]

【问题讨论】:

    标签: python django postgresql


    【解决方案1】:

    经过大量调试,我终于找到了解决方案。原因是我试图插入另外两个具有指定 idcategories,这将导致 postgresql 停止增加相对 sequencelast_value。如下:

    0002_auto_20150728_0442.py

    if not Category.objects.filter(pk=settings.ST_TOPIC_PRIVATE_CATEGORY_PK).exists():
        Category.objects.create(
            pk=settings.ST_TOPIC_PRIVATE_CATEGORY_PK,
            title="Private",
            slug="private",
            is_private=True
        )
    
    if not Category.objects.filter(pk=settings.ST_UNCATEGORIZED_CATEGORY_PK).exists():
        Category.objects.create(
            pk=settings.ST_UNCATEGORIZED_CATEGORY_PK,
            title="Uncategorized",
            slug="uncategorized"
        )
    

    解决这个问题的方法很简单,要么在django 中手动更改last_value,要么不指定id,即删除以下行:

    ....
    pk=settings.ST_TOPIC_PRIVATE_CATEGORY_PK,
    ....
    pk=settings.ST_UNCATEGORIZED_CATEGORY_PK,
    ....
    

    我猜如果让django承担管理id的任务,插入新数据时自己指定id可能不是一个好主意。

    【讨论】:

      【解决方案2】:

      我认为问题不在于您的迁移。

      您正在尝试添加多个相同的 spirit_category_category 对象,如果在 Django 测试套件中未正确配置,这些对象将触发相同的自动递增 id。一种选择是识别冲突的测试并将它们移动到单独的 TestCase 类中(因为 setUp 将为您刷新数据库)。

      另一种选择是使用像 Factory Boy 这样的库来创建模型实例,这将有助于避免这样的冲突。

      【讨论】:

      • 正如您所说,我将相同的 id 插入到表中。我删除了id 部分,一切又好了。谢谢!
      【解决方案3】:

      我遇到了同样的问题,但我需要保留 ID,因为我正在从另一台服务器恢复数据并保留关系等。 我的解决方案是在迁移文件中添加另一个命令以在插入项目后运行并重置相关表的数据库序列。

      要获得重置表序列的命令,您可以运行 python manage.py sqlsequencereset spirit,如 https://docs.djangoproject.com/en/1.9/ref/django-admin/#sqlsequencereset 中所述

      然后在您的迁移0002_auto_20150728_0442.py 文件中添加:

      from django.db connection
      
      def reset_spirit_pk_sequence(apps, schema_editor):
          with connection.cursor() as cursor:
              cursor.execute("RESULT_FROM_SQLRESETSEQUENCE")
          row = cursor.fetchone()
      
      ...
      ...
      
      operations = [
          migrations.RunPython(default_categories),
          migrations.RunPython(reset_spirit_pk_sequence),
      ]
      

      注意将RESULT_FROM_SQLRESETSEQUENCE 替换为您从manage.py sqlresetsequence 获得的与您遇到问题的表相关的命令行(用\ 转义内部")。

      【讨论】:

        【解决方案4】:

        在测试中,我的代码试图在不传递 id(主键)的情况下保存一行,但我得到:

        django.db.utils.IntegrityError: duplicate key value violates unique constraint ...
        DETAIL:  Key (id)=(1) already exists. 
        

        我解决了以下问题:

        iOneMore = Model.objects.last().id + 1
        oNew = Model( id = iOneMore, col1 = string1, col2 = string2 )
        oNew.save()
        

        问题已解决,不再出错。

        【讨论】:

          猜你喜欢
          • 2019-07-22
          • 2018-03-28
          • 2013-10-08
          • 2018-12-06
          • 2022-11-05
          • 2014-06-19
          • 2021-01-25
          • 1970-01-01
          • 2022-01-13
          相关资源
          最近更新 更多