3

I just had an issue with Django and PostgreSQL that I don't understand.

I have a simple model, defined such as:

class MyModel(models.Model):
    my_field = models.IntegerField()
    my_other_field = models.TextField()

In my view, i have something similar to:

my_object = MyModel(my_field=1, my_other_field='blah')
my_object.save()

Everything was working fine, until this morning. I got this error:

 IntegrityError at /my_url/

duplicate key value violates unique constraint "my_model_pkey"
DETAIL:  Key (id)=(3) already exists.
CONTEXT:  Remote SQL command: INSERT INTO public.my_model(id, my_field, my_other_field) VALUES ($1, $2, $3) RETURNING id

I had this error once, I know it is related to the way PostgreSQL syncs the sequential table associated with my model with the id column. I has to run this function in PostgreSQL until the id returned was greater than the biggest value of the id.

select nextval('my_model_id_seq'::regclass);

My question is: Why did this happen in the first place? And how to prevent it in the future ?

By the way, that's the only way I insert data into the table, I've never inserted data manually.

I hope the question is clear enough

4

2 回答 2

2

我认为问题不是“为什么我的序列搞砸了”——而是“为什么 Django 在插入行时试图为 id 列提供一个值,而不是允许数据库在序列中插入下一个值”。

Django 文档描述了当你调用 save() 时它用来决定是执行 UPDATE 还是 INSERT 的算法。

该算法涉及检查对象的“id”字段是否已设置为某个值。如果不是,那么它会执行 INSERT(可能没有为 'id' 字段指定值)。如果已设置,则首先尝试执行 UPDATE;如果这不会导致更新记录,那么它将执行 INSERT(这一次它可能会为“id”字段指定一个值)。

正如 Erwin 的回答中所指出的,您看到的错误消息表明它正在尝试在指定“id”字段的值时插入一行。

我注意到这个算法似乎在 Django 1.6 版中发生了变化。以前,它首先使用 SELECT 来查看记录是否存在,如果存在则使用 UPDATE,如果不存在则使用 INSERT。如果您的问题在升级后开始出现,那么这可能是一个原因。文档说明:

在极少数情况下,即使数据库包含对象主键值的行,数据库也不会报告行已更新。一个例子是返回 NULL 的 PostgreSQL ON UPDATE 触发器。在这种情况下,可以通过将 select_on_save 选项设置为 True 来恢复到旧算法。

如果这发生在您身上,那么它将解释您的症状:尝试更新数据库中的值时实际上会发生错误,并且 django 会错误地认为该行不存在然后尝试创建它。

您可以通过将“select_on_save”设置为 true 来检查这一点,以恢复旧行为。

另一个可能的原因是您的代码无意中将对象上的“id”属性设置为某个值,然后调用了 save()。这可能会导致各种问题,具体取决于数据库中是否已经存在该值。特别是,它可能会导致创建一个具有“id”值的行,该值在与该列关联的序列的当前范围之前,因此稍后您会在尝试插入该行时遇到错误。

另一个可能的原因可能是在先前从数据库加载的行上使用 save() 的“force_insert”参数(因此它实际上是您应该更新的现有行)。

于 2014-04-19T01:51:14.257 回答
1

问题的根源在这里(错误消息中的 SQL 命令):

INSERT INTO public.my_model(id, my_field, my_other_field)
VALUES ($1, $2, $3)
RETURNING id

由于您的 id 列似乎是一种serial类型,因此请勿手动插入值。让默认值自动从序列中绘制。应该:

INSERT INTO public.my_model(my_field, my_other_field)
VALUES ($1, $2)
RETURNING id;

这就是添加开头的全部要点RETURNING id:返回新生成的id. 如果您自己传递一个值,则不需要返回它。

使固定

如果序列以某种方式不同步,因为手动输入与来自 的数字冲突nextval(),请运行此查询一次

SELECT setval('my_model_id_seq', max(id)) FROM my_model;

这会将序列设置为当前最大值。下一个电话是下一个号码,没有一个错误。

于 2014-04-18T15:20:06.757 回答