如果两个进程同时修改两个事务中的数据,并且table上有一个唯一约束,会发生什么?

What will happen if two processes modify data in two transactions at the same time and there is a unique constraint on the table?

我正在考虑我正在处理的生产系统中的竞争条件。数据库是 PostgreSQL。应用程序写在Java,但这不相关。

有一个名为 "versions" 的 table,它包含列 "entity_ID" 和 "version"(以及一些其他字段)。 table 包含某个实体的版本。

有一个应用程序,用户可以在其中修改这些实体。

实体的每次修改都会为表格创建一个新版本"versions"(使用触发器)。此触发器在相同的 table "versions" 中找到最后一个版本并插入具有相同 entity_ID 的新行,但版本 = (最新版本 + 1).

在 PostgreSQL 中有一项每 运行 每 4:00 的夜间作业也会更改这些实体,因此会更新 table "versions" 中的数据。此过程旨在在早上(在应用程序的用户开始使用它之前)之前完成其工作,但不幸的是 运行s 到了一天。由于这个过程是运行在一个函数中,所以它是一个大事务。因此,它所做的更改对应用程序不可见。

夜间作业使用以下工作流程

这导致以下竞争条件发生了几次(假设 X 是实体 A 的最后一个版本):

  1. 夜间作业开始
  2. 夜间作业修改实体 A,创建版本 X+1
  3. 应用程序还用于修改实体 A,同时创建版本 X+1(因为夜间作业事务尚未提交,所以版本 X+1 对应用程序不可见)
  4. 夜间作业结束,导致 COMMIT
  5. 现在有两个版本号为 X+1 的版本,这会导致应用程序中断。

我认为我可以通过对字段(entity_ID,版本)使用 UNIQUE CONSTRAINT 来解决问题。我认为这会导致应用程序在竞争条件第 3 步收到错误(由于违反了 UNIQUE CONSTRAINT)。但我不确定在这种情况下唯一约束是如何工作的。在竞争条件第 3 步中,当应用程序添加版本时,数据库是否检查 UNIQUE CONSTRAINT?我想不会,因为夜间流程的交易还没有完成。如果我是正确的,并且仅在竞争条件第 4 步检查 UNIQUE CONSTRAINT,当提交时,这将导致整个夜间程序失败,这不是期望的结果。

所以,问题如下。

默认情况下,PostgreSQL 中的唯一约束会在每个语句的末尾进行检查。使用 psql 测试行为很容易。

一些大的红旗。 . .

As this procedure is run in a function, then it is one big transaction.

这不是一件大事,因为您 运行 正在执行一项功能。这是一项大交易,因为您还没有 运行 对较小的数据子集多次使用该函数。您是否可以 运行 子集上的函数取决于应用程序。

Iterate over entities that need to be modified

SQL 数据库的粗略经验法则:迭代总是错误的。

SQL是一种面向集合的语言。处理集合通常比迭代快,而且通常快几个数量级。

If "failed_counter" > 10, cancel work.

这看起来很可疑。为什么九次失败还可以?为什么 任何 失败都正常?

I thought that I could just solve the problem by using an UNIQUE CONSTRAINT over fields (entity_ID, version).

还没有 对这两列设置唯一约束是一个挥舞着的大红旗。先解决这个问题。

应用程序显然应该等待批处理作业完成,但实际上并没有等待,这一事实可能是也可能不是系统设计问题。 (闻起来像是系统设计问题。)

There is a nightly job that is run in PostgreSQL every 4:00 ...

你有没有想过从 3:00 开始?

对此进行测试,但不要在您的生产服务器上进行测试。

  • 放下扳机。
  • 添加类型为 timestamp with time zone 的列。
  • 设置该列的默认值。大多数应用程序将使用 current_timestamp,但您 可能 需要 clock_timestamp()Docs
  • 在{entity_id,新时间戳列}上添加唯一约束。

消除触发器可能会加快速度。