PostgreSQL:在严苛的多用户环境中,使用SELECT nextval生成器是否线程安全?

16

我的意思是,就像成千上万的用户在不同时间内更新数据库中的值?

4个回答

25

是的,nextval 可以安全地用于多个并发操作的事务中。这就是它存在的目的和原因。

尽管如此,它实际上并非 "线程安全",因为 PostgreSQL 使用的是多进程模型而不是多线程模型,而且大多数客户端驱动程序(例如 libpq)不允许一次有多个线程与单个连接进行交互。

您还应该注意,虽然 nextval 保证返回不同且递增的值,但不能保证没有 "空洞" 或 "间隙"。当生成的值被废弃而未提交时(例如通过 ROLLBACK),以及 PostgreSQL 在服务器崩溃后恢复时,就会创建这样的空洞。

虽然 nextval 总是会返回递增的数字,但这并不意味着您的事务将按照从给定序列获得 ID 的顺序提交。因此,出现以下情况是完全正常的:

Start IDs in table: [1 2 3 4]
1st tx gets ID 5 from nextval()
2nd tx gets ID 6 from nextval()
2nd tx commits:     [1 2 3 4 6]
1st tx commits:     [1 2 3 4 5 6]

换句话说,空洞可以出现和消失。

这两个异常都是使一个nextval调用不会阻塞另一个的必要和不可避免的结果。

如果您想要一个没有这种排序和间隔异常的序列,您需要使用无间隙序列设计,它只允许一个事务同时拥有一个未提交的生成ID,从而有效地消除该表中插入操作的所有并发性。这通常使用在计数器表上的SELECT FOR UPDATEUPDATE ... RETURNING实现。

搜索“PostgreSQL无间隙序列”以获得更多信息。


Craig,这是否意味着它可能为两个并发事务返回相同的值?!因此,如果我们使用它来计算ID,则可能会导致“重复键值违规”吗?谢谢。 - Tarik
@Tarik 不,它的整个目的在于它永远不会在相同或不同的事务中多次返回相同的值。但它并不保证任何关于顺序或间隙的东西。 - Craig Ringer

4

是的,它是线程安全的。

根据手册

nextval

将序列对象前进到下一个值并返回该值。这是原子性完成的:即使多个会话同时执行nextval,每个会话也会安全地接收到不同的序列值。

(强调是我加的)


他们是否假设客户端来自不同的机器?我刚刚使用两个浏览器同时插入行时触发了一个错误! - juk
@juk:是什么错误?调用nextval()可以从任意数量的事务中调用。 - user330315
1
@juk,您似乎对客户端/服务器架构的工作原理有一些基本误解。PostgreSQL不关心客户端来自哪里 - 本地主机、LAN主机或允许连接的互联网上的任何地方。对于PostgreSQL来说,它只是一个连接 - 另一个会话,具有相同的规则。在这种情况下,我怀疑PostgreSQL连接根本不是来自不同的机器;浏览器会话是,但它们将连接到一个服务器,该服务器将建立PostgreSQL连接。几乎可以肯定,浏览器不会直接连接到PostgreSQL。 - Craig Ringer
@CraigRinger 当然了,我已经抽象化了"浏览器",它可以通过tornadoweb和psycopg模块实现。 - juk
@juk...在这种情况下,所有连接都来自同一主机,但这并不重要。 - Craig Ringer

2

是的: http://www.postgresql.org/docs/current/static/functions-sequence.html

否则它就没有用处了。

编辑: 以下是nextval和currval的使用方法:

nextval返回一个新的序列号,您可以在第一张表的插入中使用它作为id

currval返回此会话获取的最后一个序列号,您可以在外键中使用它来引用第一张表

每次调用nextval都会返回另一个值,在相同的插入集中不要调用两次。

当然,在任何多用户代码中都应该使用事务。


如果我同时从两个不同的浏览器添加数据,为什么会出现错误:IntegrityError: insert or update on table "category" violates foreign key constraint "category_category_id_fkey" 详细信息:键(category_id)=(60)在表"book"中不存在。 上下文:SQL语句"INSERT INTO schemas.category ( category_id, name ) VALUES ( in_category_id, in_category )" PL/pgSQL函数"book_add"第39行的SQL语句。 - juk
如果您有一个具体的问题需要调试,请在问题中提供详细信息。 - David Aldridge
3
@juk,你的代码肯定存在一个错误。由于我们无法看到你的代码,我不能多说。请更新你的问题,附上你正在运行的代码和表定义,或许我们能够帮助你。提示:错误信息显示你违反了外键约束而不是唯一约束,因此我猜测你试图引用一个不存在的行。也许你期望nextval在单个事务中重复返回相同的ID吗?它并不会这样做。当你更新你的问题时,请包括你的PostgreSQL版本、所有代码以及完整的错误信息文本。 - Craig Ringer
我已经添加了更多关于如何使用它们的讨论,它们基本上与Oracle序列相同,但语法略有不同。 - Peter Wooster
实际上,此时juk应该创建一个新的问题,因为这个问题已经被明确回答了。在他的新问题中,他应该努力创建一个可重现的测试用例,以便其他人可以获得相同的结果。请注意,在设计可重现的测试用例时,你正在寻找的答案往往会变得显而易见,然后你就不需要发布问题了。如果发生这种情况,他应该仍然发布它,并附上自己的答案,以便其他人可以从中学习。 - Scott Marlowe

1
这个海报在同一个有缺陷的代码上提出了不同的问题。 这里 的重点是:他似乎不知道外键是如何工作的,并且将它们反转了(序列作为外键有点尴尬,我个人认为)。
顺便说一下:这应该是一条评论,而不是答案;但我还不能发表评论。

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