2012-04-21 90 views
5

从文档find_or_create如何在使用DBIx :: Class :: ResultSet的find_or_create方法时避免竞争条件?

注意:由于find_or_create()从数据库中读取信息,然后基于该结果 可能插入,该方法是受a种族 条件。在 查找已完成且在创建开始之前,另一个进程可以在表中创建一条记录。为避免 这个问题,请在事务中使用find_or_create()。

在PostgreSQL的事务中使用find_or_create()足够吗?

回答

6

不,文档不正确。单独使用交易而不是避免此问题。它只能保证如果发生异常时整个事务会回滚 - 这样就不会有任何不一致的状态持久存在数据库中。

避免这个问题你必须锁定表 - 在一个事务中,因为所有的锁都在事务结束时被释放。类似于:

BEGIN; 
LOCK TABLE mytbl IN SHARE MODE; 

-- do your find_or_create here 

COMMIT; 

但是,这并不是万能的万能药。它可能会成为一个性能问题,并且可能有死锁(并发事务相互试图锁定另一个已经锁定的资源)。 PostgreSQL将检测到这种情况并取消除竞争事务之外的所有事务。您必须准备好在失败时重试操作。

The PostgreSQL manual about locks.

如果你没有很多并发的也可能只是忽略的问题。时隙非常小,所以它实际上很少发生。如果您捕获重复的密钥违规错误,这将不会造成任何损害,那么您也已经介绍了这一点。

+2

其他有用的页面。 Docs:http://www.postgresql.org/docs/current/interactive/mvcc.html PostgreSQL 9.1或更高版本的可串行化实现:http://wiki.postgresql.org/wiki/SSI其他隔离级别或PostgreSQL版本:http ://www.postgresql.org/files/developer/concurrency.pdf – kgrittn 2012-04-22 00:27:17

+0

但是,在DBIC中捕捉“重复密钥违规错误”的正确方法是什么? – 2012-10-15 21:37:21

+0

@eugeney:我想你会为此提出一个新问题,而不是评论。你总是可以链接到这一个,以节省自己的一些打字。 – 2012-10-15 22:09:19

0

这个实现的find_or_create应防止竞争条件,在OP描述:

eval { 
    $row = $self->model->create({ ... }); 
} 
if([email protected] && [email protected] =~ /duplicate/i) { 
    $row = $self->model->find({ ... }); 
} 

它还减少find_or_create()在最好的情况下,单一的查询。

+1

这反转了逻辑。但是现在你有一个很小的时间段,其中的条目可以被删除 - 在这种情况下,逻辑将失败。试图写第一个比尝试阅读更昂贵。所以这只是一个改进,如果重复是非常罕见的。不管怎样,冲突应该是非常罕见的,因为时间间隔很小。 – 2012-04-22 13:35:50