来自find_or_create
的文档:
注意:因为find_or_create()从数据库读取然后 可能根据结果插入,这种方法受比赛影响 条件。另一个进程可以在表中创建记录 查找已完成,并且在创建开始之前。避免 这个问题,在事务中使用find_or_create()。
仅仅在PostgreSQL中的事务中使用find_or_create()
是否足够?
答案 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()
简化为单个查询。