从文档中find_or_create
:
注意:因为 find_or_create() 从数据库中读取,然后可能会根据结果进行插入,所以此方法受制于竞争条件。另一个进程可以在查找完成之后和创建开始之前在表中创建一条记录。为避免此问题,请在事务中使用 find_or_create()。
find_or_create()
仅在 PostgreSQL 的事务中使用就足够了吗?
从文档中find_or_create
:
注意:因为 find_or_create() 从数据库中读取,然后可能会根据结果进行插入,所以此方法受制于竞争条件。另一个进程可以在查找完成之后和创建开始之前在表中创建一条记录。为避免此问题,请在事务中使用 find_or_create()。
find_or_create()
仅在 PostgreSQL 的事务中使用就足够了吗?
不,文档不正确。单独使用事务并不能避免这个问题。它只保证在发生异常时回滚整个事务——这样就不会将不一致的状态持久化到数据库中。
为避免此问题,您必须在事务中锁定表,因为所有锁定都在事务结束时释放。就像是:
BEGIN;
LOCK TABLE mytbl IN SHARE MODE;
-- do your find_or_create here
COMMIT;
但这并不是万能的灵丹妙药。它可能会成为性能问题,并且可能会出现死锁(并发事务相互尝试锁定对方已经锁定的资源)。PostgreSQL 将检测到这种情况并取消除一个竞争事务之外的所有事务。您必须准备好在失败时重试操作。
如果你没有很多并发,你也可以忽略这个问题。时隙非常小,因此实际上很少发生。如果您发现重复的密钥违规错误,这不会造成任何伤害,那么您也已经涵盖了这一点。
此实现find_or_create
应防止竞争条件,如 OP 中所述:
eval {
$row = $self->model->create( { ... } );
}
if($@ && $@ =~ /duplicate/i) {
$row = $self->model->find( { ... } );
}
在最好的情况下,它还简化find_or_create()
为单个查询。