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

时间:2012-04-21 23:44:46

标签: perl postgresql database dbix-class

来自find_or_create的文档:

  

注意:因为find_or_create()从数据库读取然后   可能根据结果插入,这种方法受比赛影响   条件。另一个进程可以在表中创建记录   查找已完成,并且在创建开始之前。避免   这个问题,在事务中使用find_or_create()。

仅仅在PostgreSQL中的事务中使用find_or_create()是否足够?

2 个答案:

答案 0 :(得分:6)

不,文档不正确。仅使用交易可以避免此问题。它只保证在发生异常时回滚整个事务 - 这样就不会将不一致的状态保存到数据库中。

避免此问题,您必须锁定表 - 在事务内部,因为所有锁都在事务结束时释放。类似的东西:

BEGIN;
LOCK TABLE mytbl IN SHARE MODE;

-- do your find_or_create here

COMMIT;

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

The PostgreSQL manual about locks.

如果你没有很多并发性,你可能也会忽略这个问题。时间段非常小,所以实际上很少发生。如果您发现重复的密钥违规错误,这不会造成任何损害,那么您也已经涵盖了这一点。

答案 1 :(得分:0)

find_or_create的这种实现应该可以防止竞争条件,如OP:

中所述
eval {
    $row = $self->model->create( { ... } );
}
if($@ && $@ =~ /duplicate/i) {
   $row = $self->model->find( { ... } );
} 

在最佳情况下,它还会将find_or_create()简化为单个查询。