如果两个进程同时修改两个事务中的数据,并且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 到了一天。由于这个过程是运行在一个函数中,所以它是一个大事务。因此,它所做的更改对应用程序不可见。
夜间作业使用以下工作流程:
- 设置"failed_counter" = 0
- 迭代需要修改的实体
- 对 BEGIN .. EXCEPTION .. END 块中的实体进行修改
- 如果有异常,增加"failed_counter"。将异常和失败的实体记录到日志 table.
- 如果 "failed_counter" > 10,取消工作。
- 结束工作
这导致以下竞争条件发生了几次(假设 X 是实体 A 的最后一个版本):
- 夜间作业开始
- 夜间作业修改实体 A,创建版本 X+1
- 应用程序还用于修改实体 A,同时创建版本 X+1(因为夜间作业事务尚未提交,所以版本 X+1 对应用程序不可见)
- 夜间作业结束,导致 COMMIT
- 现在有两个版本号为 X+1 的版本,这会导致应用程序中断。
我认为我可以通过对字段(entity_ID,版本)使用 UNIQUE CONSTRAINT 来解决问题。我认为这会导致应用程序在竞争条件第 3 步收到错误(由于违反了 UNIQUE CONSTRAINT)。但我不确定在这种情况下唯一约束是如何工作的。在竞争条件第 3 步中,当应用程序添加版本时,数据库是否检查 UNIQUE CONSTRAINT?我想不会,因为夜间流程的交易还没有完成。如果我是正确的,并且仅在竞争条件第 4 步检查 UNIQUE CONSTRAINT,当提交时,这将导致整个夜间程序失败,这不是期望的结果。
所以,问题如下。
- 何时检查 UNIQUE CONSTRAINT:在竞争条件第 3 步或竞争条件第 4 步?
- 如果最后一个问题的答案是"race condition 4",那我该如何改变系统的设计来避免上述问题呢?
默认情况下,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,新时间戳列}上添加唯一约束。
消除触发器可能会加快速度。
我正在考虑我正在处理的生产系统中的竞争条件。数据库是 PostgreSQL。应用程序写在Java,但这不相关。
有一个名为 "versions" 的 table,它包含列 "entity_ID" 和 "version"(以及一些其他字段)。 table 包含某个实体的版本。
有一个应用程序,用户可以在其中修改这些实体。
实体的每次修改都会为表格创建一个新版本"versions"(使用触发器)。此触发器在相同的 table "versions" 中找到最后一个版本并插入具有相同 entity_ID 的新行,但版本 = (最新版本 + 1).
在 PostgreSQL 中有一项每 运行 每 4:00 的夜间作业也会更改这些实体,因此会更新 table "versions" 中的数据。此过程旨在在早上(在应用程序的用户开始使用它之前)之前完成其工作,但不幸的是 运行s 到了一天。由于这个过程是运行在一个函数中,所以它是一个大事务。因此,它所做的更改对应用程序不可见。
夜间作业使用以下工作流程:
- 设置"failed_counter" = 0
- 迭代需要修改的实体
- 对 BEGIN .. EXCEPTION .. END 块中的实体进行修改
- 如果有异常,增加"failed_counter"。将异常和失败的实体记录到日志 table.
- 如果 "failed_counter" > 10,取消工作。
- 结束工作
这导致以下竞争条件发生了几次(假设 X 是实体 A 的最后一个版本):
- 夜间作业开始
- 夜间作业修改实体 A,创建版本 X+1
- 应用程序还用于修改实体 A,同时创建版本 X+1(因为夜间作业事务尚未提交,所以版本 X+1 对应用程序不可见)
- 夜间作业结束,导致 COMMIT
- 现在有两个版本号为 X+1 的版本,这会导致应用程序中断。
我认为我可以通过对字段(entity_ID,版本)使用 UNIQUE CONSTRAINT 来解决问题。我认为这会导致应用程序在竞争条件第 3 步收到错误(由于违反了 UNIQUE CONSTRAINT)。但我不确定在这种情况下唯一约束是如何工作的。在竞争条件第 3 步中,当应用程序添加版本时,数据库是否检查 UNIQUE CONSTRAINT?我想不会,因为夜间流程的交易还没有完成。如果我是正确的,并且仅在竞争条件第 4 步检查 UNIQUE CONSTRAINT,当提交时,这将导致整个夜间程序失败,这不是期望的结果。
所以,问题如下。
- 何时检查 UNIQUE CONSTRAINT:在竞争条件第 3 步或竞争条件第 4 步?
- 如果最后一个问题的答案是"race condition 4",那我该如何改变系统的设计来避免上述问题呢?
默认情况下,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,新时间戳列}上添加唯一约束。
消除触发器可能会加快速度。