为什么我需要运行postgresql的nextval函数?如何防止它?

3

我刚刚遇到了一个Django和PostgreSQL相关的问题,但是我不理解其中的原因。

我有一个简单的模型,定义如下:

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

在我的看法中,我有一些类似的东西:

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

一切都很好,直到今天早上。我遇到了这个错误:

 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

我曾经遇到过这个错误,我知道它与PostgreSQL同步我的模型关联的顺序表和id列的方式有关。我必须在PostgreSQL中运行此函数,直到id返回的值大于id的最大值。

select nextval('my_model_id_seq'::regclass);

我想问的是:为什么这种情况会发生?以及如何避免将来再次发生?
顺便说一下,这是我唯一向表中插入数据的方式,我从未手动插入过数据。
希望我的问题已经足够清楚了。

显然你的 my_model_id_seq 出了问题。 - njzk2
是的,我明白了,但问题是为什么?这是 Django 的方式吗?这是 PostgreSQL 的问题吗(似乎不太可能),还是其他原因? - Paco
2个回答

2
我认为问题不是“为什么我的序列被搞乱了”,而是“为什么Django在插入行时尝试提供id列的值,而不是允许数据库插入下一个序列值”。 Django文档描述了它在调用save()时决定是执行UPDATE还是INSERT的算法。
该算法涉及检查对象的“id”字段是否已设置为某个值。如果没有设置,则进行INSERT(可能不指定“id”字段的值)。如果已经设置,则首先尝试执行UPDATE;如果更新记录失败,则会执行INSERT(这次可能会指定“id”字段的值)。
正如Erwin的答案中所指出的,您看到的错误消息表明它正在尝试插入一行并指定“id”字段的值。
我注意到,在Django 1.6版本中,这个算法似乎已经改变了。以前它会先使用SELECT来查看记录是否存在,然后如果存在就使用UPDATE,否则就使用INSERT。如果您的问题是在升级后开始出现的,那么这可能是一个原因。文档指出:
“有一些罕见的情况下,即使数据库包含对象主键值的行,数据库也不会报告该行已更新。例如,PostgreSQL ON UPDATE触发器返回NULL。在这种情况下,可以通过将select_on_save选项设置为True来恢复旧的算法。”
如果您遇到这种情况,那么它将解释您的症状:当尝试更新数据库中的值时,错误实际上会发生,并且Django会错误地认为该行不存在,然后尝试创建它。
您可以通过将“select_on_save”设置为true来检查此问题,以恢复旧的行为。
另一个可能的原因是,如果您的代码无意中将“id”属性设置为某个值,然后调用save()。这可能会导致各种问题,具体取决于该值是否已存在于数据库中。特别是,它可能会导致创建一行,其“id”值超出了与列关联的序列的当前范围,因此稍后插入该行时会出现错误。
另一个可能的原因是,在先前从数据库加载(因此实际上应该更新的)行上使用“force_insert”参数来保存()。

谢谢你的回答。唯一可能的情况是第一个。下次问题再次出现时,我会尝试这个方法。 - Paco

1
问题的根源在这里(来自错误信息的SQL命令):
INSERT INTO public.my_model(<b>id, </b>my_field, my_other_field)
VALUES (<b>$1, </b>$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;

这将序列设置为当前最大值。下一次调用是下一个数字,没有偏差误差。

我知道,我并不是自己发起请求,而是在执行my_object.save()时Django会自动发起请求。我不知道为什么现在出了问题。我们使用Django已经很长时间了,以前从来没有遇到过这个问题。 - Paco
@Paco:我不知道它是怎么失步的,看起来数据是手动输入的,而Django则明确地从序列中提取值以在下一次调用中插入它们(这相当低效)。我添加了一个快速修复。 - Erwin Brandstetter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接