如何重置 Postgres 主键序列何时不同步?
我遇到了我的主键序列与表行不同步的问题。
也就是说,当我插入新行时,我收到重复键错误,因为串行数据类型中隐含的序列返回一个已经存在的数字。
这似乎是由于导入/恢复未正确维护序列引起的。
I ran into the problem that my primary key sequence is not in sync with my table rows.
That is, when I insert a new row I get a duplicate key error because the sequence implied in the serial datatype returns a number that already exists.
It seems to be caused by import/restores not maintaining the sequence properly.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(30)
来源 - Ruby 论坛
Source - Ruby Forum
pg_get_serial_sequence
可用于避免关于序列名称的任何错误假设。 这会一次性重置序列:或者更简洁地说:
但是,这种形式无法正确处理空表,因为 max(id) 为空,并且您也不能 setval 0,因为它超出了序列的范围。 解决此问题的一种方法是采用 ALTER SEQUENCE 语法,即
但 ALTER SEQUENCE 的用途有限,因为序列名称和重新启动值不能是表达式。
似乎最好的通用解决方案是使用 false 作为第三个参数调用 setval ,允许我们指定“下一个要使用的值”:
这勾选了我的所有框:
序列中的漏洞
最后,请注意 pg_get_serial_sequence 仅当序列归列所有时才有效。 如果递增列被定义为
serial
类型,就会出现这种情况,但是,如果手动添加序列,则必须确保ALTER SEQUENCE .. OWNED BY
也被定义执行。即,如果使用
serial
类型创建表,则这应该全部有效:但是如果手动添加序列:
pg_get_serial_sequence
can be used to avoid any incorrect assumptions about the sequence name. This resets the sequence in one shot:Or more concisely:
However this form can't handle empty tables correctly, since max(id) is null, and neither can you setval 0 because it would be out of range of the sequence. One workaround for this is to resort to the
ALTER SEQUENCE
syntax i.e.But
ALTER SEQUENCE
is of limited use because the sequence name and restart value cannot be expressions.It seems the best all-purpose solution is to call
setval
with false as the 3rd parameter, allowing us to specify the "next value to use":This ticks all my boxes:
hole in the sequence
Finally, note that
pg_get_serial_sequence
only works if the sequence is owned by the column. This will be the case if the incrementing column was defined as aserial
type, however if the sequence was added manually it is necessary to ensureALTER SEQUENCE .. OWNED BY
is also performed.i.e. if
serial
type was used for table creation, this should all work:But if sequences were added manually:
最短、最快的方式
tbl_id
是表tbl
的serial
或IDENTITY
列,从序列中绘制>tbl_tbl_id_seq
(生成的默认名称)。 请参阅:如果您不这样做不知道附加序列的名称(不必采用默认形式),请使用
pg_get_serial_sequence()
(也适用于IDENTITY
):有这里没有差一错误。 手册:
大胆强调我的。
如果表格可以为空,并且在这种情况下实际上从 1 开始:
我们不能只使用 2 参数形式并从
0
开始,因为下界默认情况下,序列数为 1(除非自定义)。在并发写入负载下安全
为了同时防御并发序列活动或写入,请在
SHARE
模式下锁定表。 它可以防止并发事务写入更高的数字(或任何东西)。还要考虑到客户端可能已经提前获取了序列号,而没有对主表进行任何锁定(在某些设置中可能会发生),仅增加序列的当前值,而不是减少它。 这可能看起来有些偏执,但这符合序列的本质和防范并发问题。
SHARE
模式足以满足此目的。 手册:它与 ROW EXCLUSIVE 模式冲突。
The shortest and fastest way
tbl_id
being theserial
orIDENTITY
column of tabletbl
, drawing from the sequencetbl_tbl_id_seq
(resulting default name). See:If you don't know the name of the attached sequence (which doesn't have to be in default form), use
pg_get_serial_sequence()
(works forIDENTITY
, too):There is no off-by-one error here. The manual:
Bold emphasis mine.
If the table can be empty, and to actually start from 1 in this case:
We can't just use the 2-parameter form and start with
0
because the lower bound of sequences is 1 by default (unless customized).Safe under concurrent write load
To also defend against concurrent sequence activity or writes, lock the table in
SHARE
mode. It keeps concurrent transactions from writing a higher number (or anything at all).To also take clients into account that may have fetched sequence numbers in advance without any locks on the main table, yet (can happen in certain setups), only increase the current value of the sequence, never decrease it. That may seem paranoid, but that's in accord with the nature of sequences and defending against concurrency issues.
SHARE
mode is strong enough for the purpose. The manual:It conflicts with
ROW EXCLUSIVE
mode.这将从公共重置所有序列,不对表或列名称做出任何假设。 在8.4版本上测试
This will reset all sequences from public making no assumptions about table or column names. Tested on version 8.4
ALTER SEQUENCE 序列名 RESTART WITH (SELECT max(id) FROM table_name);不起作用。
复制自 @tardate 答案:
ALTER SEQUENCE sequence_name RESTART WITH (SELECT max(id) FROM table_name);Doesn't work.
Copied from @tardate answer:
在下面的示例中,表名称为
users
,架构名称为public
(默认架构),替换根据您的需要。1. 检查
最大 id
:2. 检查
下一个值
:3. 如果
下一个值
低于最大 id
,重置它:注意:
nextval()
将在返回当前值之前递增序列,而currval()
只会返回当前值,如文档所述 < a href="https://www.postgresql.org/docs/current/functions-sequence.html" rel="noreferrer">此处。In the example below, the table name is
users
and the schema name ispublic
(default schema), replace it according to your needs.1. Check the
max id
:2. Check the
next value
:3. If the
next value
is lower than themax id
, reset it:Note:
nextval()
will increment the sequence before returning the current value whilecurrval()
would just return the current value, as documented here.该命令仅用于更改 postgresql 中自动生成的键序列值
您可以在零处放置要重新启动序列的任何数字。
默认序列名称为“TableName_FieldName_seq”。 例如,如果您的表名称为
"MyTable"
并且字段名称为"MyID"
,则您的序列名称将为"MyTable_MyID_seq"< /代码>
。
这个答案与 @murugesanponappan 的答案相同,但他的解决方案中存在语法错误。 您不能在
alter
命令中使用子查询(select max()...)
。 因此,要么必须使用固定数值,要么需要使用变量来代替子查询。This command for only change auto generated key sequence value in postgresql
In place of zero you can put any number from which you want to restart sequence.
default sequence name will
"TableName_FieldName_seq"
. For example, if your table name is"MyTable"
and your field name is"MyID"
, then your sequence name will be"MyTable_MyID_seq"
.This is answer is same as @murugesanponappan's answer, but there is a syntax error in his solution. you can not use sub query
(select max()...)
inalter
command. So that either you have to use fixed numeric value or you need to use a variable in place of sub query.重置所有序列,除了每个表的主键是“id”之外,不做任何关于名称的假设:
Reset all sequences, no assumptions about names except that the primary key of each table is "id":
我建议在 postgres wiki 上找到这个解决方案。 它会更新表的所有序列。
如何使用(来自 postgres wiki):
示例:
原始文章(还修复了序列所有权)此处
I suggest this solution found on postgres wiki. It updates all sequences of your tables.
How to use(from postgres wiki):
Example:
Original article(also with fix for sequence ownership) here
当序列名称、列名称、表名称或模式名称具有有趣的字符(例如空格、标点符号等)时,这些函数就会充满危险。 我是这么写的:
您可以通过向其传递 OID 来调用单个序列,它将返回任何将该序列作为默认值的表使用的最大数字; 或者您可以使用这样的查询运行它,以重置数据库中的所有序列:
使用不同的限定,您可以仅重置特定模式中的序列,依此类推。 例如,如果您想调整“公共”模式中的序列:
请注意,由于 setval() 的工作原理,您不需要向结果添加 1。
作为结束语,我必须警告一些数据库似乎具有链接到序列的默认值,其方式不允许系统目录拥有它们的完整信息。 当您在 psql 的 \d 中看到类似的内容时,就会发生这种情况:
请注意,默认子句中的 nextval() 调用除了 ::regclass 转换之外还有 ::text 转换。 我认为这是由于数据库是从旧的 PostgreSQL 版本中进行 pg_dump 造成的。 将会发生的是上面的函数sequence_max_value()将忽略这样的表。 要解决此问题,您可以重新定义 DEFAULT 子句以直接引用序列而不进行强制转换:
然后 psql 正确显示它:
一旦您解决了这个问题,该函数就可以正确地对该表以及所有其他可能的表起作用使用相同的顺序。
These functions are fraught with perils when sequence names, column names, table names or schema names have funny characters such as spaces, punctuation marks, and the like. I have written this:
You can call it for a single sequence by passing it the OID and it will return the highest number used by any table that has the sequence as default; or you can run it with a query like this, to reset all the sequences in your database:
Using a different qual you can reset only the sequence in a certain schema, and so on. For example, if you want to adjust sequences in the "public" schema:
Note that due to how setval() works, you don't need to add 1 to the result.
As a closing note, I have to warn that some databases seem to have defaults linking to sequences in ways that do not let the system catalogs have full information of them. This happens when you see things like this in psql's \d:
Note that the nextval() call in that default clause has a ::text cast in addition to the ::regclass cast. I think this is due to databases being pg_dump'ed from old PostgreSQL versions. What will happen is that the function sequence_max_value() above will ignore such a table. To fix the problem, you can redefine the DEFAULT clause to refer to the sequence directly without the cast:
Then psql displays it properly:
As soon as you've fixed that, the function works correctly for this table as well as all others that might use the same sequence.
还有另一个 plpgsql - 仅当
max(att) > 时才重置 然后lastval
也注释行
--execute format('alterequence
将给出列表,而不是实际重置值Yet another plpgsql - resets only if
max(att) > then lastval
also commenting the line
--execute format('alter sequence
will give the list, not actually resetting the value从公共重置所有序列
Reset all sequence from public
当我使用实体框架创建数据库,然后使用初始数据为数据库播种时,会发生此问题,这会导致序列不匹配。
我通过创建一个在数据库播种后运行的脚本来解决这个问题:
This issue happens with me when using entity framework to create the database and then seed the database with initial data, this makes the sequence mismatch.
I Solved it by Creating a script to run after seeding the database:
这里有一些非常核心的答案,我假设在被问到这个问题时它曾经非常糟糕,因为这里的很多答案不适用于 9.3 版本。 自版本 8.0 以来的文档提供了这个问题的答案:
另外,如果您需要处理区分大小写的序列名称,可以这样做:
Some really hardcore answers here, I'm assuming it used to be really bad at around the time when this has been asked, since a lot of answers from here don't works for version 9.3. The documentation since version 8.0 provides an answer to this very question:
Also, if you need to take care of case-sensitive sequence names, that's how you do it:
我的版本使用第一个,并进行一些错误检查......
My version use the first one, with some error checking...
将它们放在一起
将修复给定表的 '
id'
序列(例如,django 通常需要这样做)。Putting it all together
will fix '
id'
sequence of the given table (as usually necessary with django for instance).重新检查公共模式函数中的所有序列
Recheck all sequence in public schema function
在我还没有尝试过代码之前:在下面我发布
Klaus 和 user457226 解决方案的 sql 代码版本
它可以在我的电脑 [Postgres 8.3] 上运行,只需进行一些小的调整
对于 Klaus 版本和我的 user457226 版本。
克劳斯解决方案:
user457226 解决方案:
before I had not tried yet the code : in the following I post
the version for the sql-code for both Klaus and user457226 solutions
which worked on my pc [Postgres 8.3], with just some little adjustements
for the Klaus one and of my version for the user457226 one.
Klaus solution :
user457226 solution :
这个答案是毛罗的副本。
This answer is a copy from mauro.
所以我可以看出这个帖子中没有足够的意见或重新发明的轮子,所以我决定让事情变得有趣。
下面是一个过程:
So I can tell there aren't enough opinions or reinvented wheels in this thread, so I decided to spice things up.
Below is a procedure that:
如果您在加载自定义 SQL 数据进行初始化时看到此错误,避免这种情况的另一种方法是:
代替编写:
从初始数据中删除
id
(主键)这使 Postgres 序列保持同步!
If you see this error when you are loading custom SQL data for initialization, another way to avoid this is:
Instead of writing:
Remove the
id
(primary key) from initial dataThis keeps the Postgres sequence in sync !
要将所有序列重新启动为 1,请使用:
To restart all sequence to 1 use:
只需运行以下命令:
Just run below command:
克劳斯的回答是最有用的,除了一点点错过:你
必须在 select 语句中添加 DISTINCT。
但是,如果您确定没有表名+列名可以等效
对于两个不同的表,您还可以使用:
这是 user457226 解决方案的扩展,适用于以下情况
一些感兴趣的列名称不是“ID”。
The Klaus answer is the most useful, execpt for a little miss : you
have to add DISTINCT in select statement.
However, if you are sure that no table+column names can be equivalent
for two different tables, you can also use :
which is an extension of user457226 solution for the case when
some interested column name is not 'ID'.
我花了一个小时试图获得 djsnowsill 的答案,以使用混合大小写表和列来处理数据库,然后由于 Manuel Darveau 的评论,最终偶然发现了解决方案,但我想我可以让每个人都更清楚一点:
这有好处:
解释一下,问题在于
pg_get_serial_sequence
使用字符串来计算出您所指的内容,因此如果您这样做:这是使用
''%1$I''
在格式字符串中,''
形成撇号1$
表示第一个参数,I
表示在引号中I spent an hour trying to get djsnowsill's answer to work with a database using Mixed Case tables and columns, then finally stumbled upon the solution thanks to a comment from Manuel Darveau, but I thought I could make it a bit clearer for everyone:
This has the benefit of:
To explain, the problem was that
pg_get_serial_sequence
takes strings to work out what you're referring to, so if you do:This is achieved using
''%1$I''
in the format string,''
makes an apostrophe1$
means first arg, andI
means in quotes更新架构中用作 ID 的所有序列的方法:
A method to update all sequences in your schema that are used as an ID:
上述答案都不适合我,因为我正在截断并重新插入单元测试的种子数据。
如果表的状态可以是空或有行,则需要使用以下内容:
COUNT(id::integer) > 0
是为了防止如果表为空则序列被设置为1。你希望序列等于 0,这样当 postgres 内部调用 nextval() 时,它将返回 1,但你不能手动将 0 传递到序列函数中(令人困惑吧……)。
我假设您的 id 列不可为空。 如果它可为空,则计数可能无法按预期运行。
None of the above answers work for me because I am truncating and reinserting seed data for unit tests.
If the state of the table can be empty OR have rows you need to use the following:
The
COUNT(id::integer) > 0
is to prevent the sequence from being set to 1 if the table is empty.You want the sequence to equal 0 so when postgres internally calls nextval() it will return 1, but you cannot manually pass 0 into the sequence function (confusing right...).
I am assuming your id column is NOT NULLABLE. If it is nullable then the count may not function as expected.
使用一些 shell 魔法来修复它的丑陋黑客,不是一个很好的解决方案,但可能会启发其他有类似问题的人:)
Ugly hack to fix it using some shell magic, not a great solution but might inspire others with similar problems :)