鉴于表table_a
和table_b
,我需要在更新table_a
时可靠地(并发地)执行这样的操作:
SELECT
来自table_a
的一些行。UPDATE
table_b
中的一行。我需要防范这种情况发生(请调用此方案A),其中table_b
结束反映旧版table_a
:
worker1
从table_a
获取行。table_a
已更新。worker2
从table_a
获取行。worker2
更新table_b
。worker1
更新table_b
。但这很好(称之为方案B)因为table_b
结束了正确的状态:
worker1
从table_a
获取行。table_a
已更新。worker2
从table_a
获取行。worker1
更新table_b
。worker2
更新table_b
。一种解决方案是将整个事物包装在REPEATABLE READ
事务中。然后事件A中worker1
的事务失败,而事件B中worker2
的事务失败。如果没有区分方案的方法,唯一的选择是重试失败的事务。
但在两种情况下都是浪费:在方案A中,我们宁愿不重试worker1
的交易,因为table_b
已经完全更新。在方案B中,我们首先不会失败worker2
的交易,因为它做的是正确的。
如果我们从开始(table_b
)知道:b_id
中行的主键,并且每个工作人员都有一些唯一ID(:worker_id
),我们可以尝试其他方法。向mark
添加table_b
列,让每个工作人员在第1步之前执行此操作:
UPDATE table_b SET mark = :worker_id WHERE id = :b_id;
然后在步骤3中添加WHERE
子句:
UPDATE table_b SET ... WHERE ... AND mark = :worker_id;
现在worker1
根据需要在两个方案中不更新步骤3中的行。
行标记是否合理?我遗漏了哪些缺点?什么是这个问题的“规范”解决方案?
澄清:我正在使用PostgreSQL。
答案 0 :(得分:2)
在表b上使用事务和SELECT ... FOR UPDATE。
这将导致行锁定,这将阻止worker 2更新表b,直到worker 1提交。如果使用select for update启动工作流,则在worker 1提交之前,worker 2无法启动。
第二种方法(可能更好)是将更新包装在一个语句中,该语句将执行select,因此它是单个语句,并将自动处理锁定。
行标记需要作为一个想法丢弃,因为在worker 1提交之前,worker 2将不会看到标记的行....