我正在尝试创建一个Ruby脚本,该脚本会生成多个并发子进程,每个进程都需要访问同一个数据存储(某种类型的队列)并对数据执行某些操作。问题是每行数据只应处理一次,子进程无法知道另一个子进程是否可能在同一时刻对同一数据进行操作。
我还没有选择数据存储,但我倾向于PostgreSQL只是因为它是我习惯的。我已经看到以下SQL片段建议作为避免竞争条件的方法,因为UPDATE子句可能会在SELECT发生之前锁定表行:
UPDATE jobs
SET status = 'processed'
WHERE id = (
SELECT id FROM jobs WHERE status = 'pending' LIMIT 1
) RETURNING id, data_to_process;
但这真的有效吗?在执行SELECT之前,Postgres(或任何其他数据库)可以锁定表行似乎并不直观,因为必须执行SELECT以确定需要锁定哪个表行以进行更新。换句话说,我担心这个SQL片段不会真正阻止两个独立的进程在同一个表行上进行选择和操作。
我是偏执狂吗?还有比传统RDBMS更好的选择来处理这样的并发情况吗?
答案 0 :(得分:2)
正如你所说,使用队列。 PostgreSQL中的标准解决方案是PgQ。它解决了所有这些并发问题。
答案 1 :(得分:0)
您真的想要许多必须在单个数据存储上串行运行的并发子进程吗?我建议你创建一个只能访问数据库的编写器进程(无论你使用什么),并接受来自其他进程的请求来执行你想要的数据库操作。然后在该线程中进行适当的队列管理,而不是让数据库执行它,并确保只有一个进程可以随时访问数据库。
答案 2 :(得分:0)
您描述的情况称为“不可重复读取”。有两种方法可以解决这个问题。
首选方法是将事务隔离级别设置为至少REPEATABLE READ
。这将意味着您描述的性质的并发更新将失败的任何行。如果两个进程在重叠事务中更新相同的行,则其中一个将被取消,其更改将被忽略,并将返回错误。该交易将不得不重审。这可以通过调用
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
在交易开始时。我似乎无法找到解释为ruby执行此操作的惯用方法的文档;你可能必须明确地发出那个sql。
另一个选项是manage the locking of tables explicitly,这可能导致事务阻塞(并可能死锁),直到表空闲。交易不会像上面那样失败,但争用会更高,因此我不会详细描述。
答案 3 :(得分:0)
这与我编写pg_message_queue时采用的方法非常接近,这是PostgreSQL的简单队列实现。与PgQ不同,它不需要使用PostgreSQL之外的任何组件。
它会正常工作。 MVCC将来救援。