【问题标题】:How to avoid race conditions when using the find_or_create method of DBIx::Class::ResultSet?使用 DBIx::Class::ResultSet 的 find_or_create 方法时如何避免竞争条件?
【发布时间】:2012-04-22 20:09:56
【问题描述】:

来自find_or_create 的文档:

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

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

【问题讨论】:

    标签: perl postgresql database dbix-class


    【解决方案1】:

    不,文档不正确。单独使用事务不能避免这个问题。它只保证在发生异常时回滚整个事务——这样就不会将不一致的状态持久化到数据库中。

    避免这个问题,您必须在事务中锁定表,因为所有的锁都会在事务结束时释放。比如:

    BEGIN;
    LOCK TABLE mytbl IN SHARE MODE;
    
    -- do your find_or_create here
    
    COMMIT;
    

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

    The PostgreSQL manual about locks.

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

    【讨论】:

    • 其他有用的页面。文档:postgresql.org/docs/current/interactive/mvcc.html PostgreSQL 9.1 或更高版本可序列化实现:wiki.postgresql.org/wiki/SSI 其他隔离级别或 PostgreSQL 版本:postgresql.org/files/developer/concurrency.pdf
    • 但是在 DBIC 中捕获“重复密钥违规错误”的正确方法是什么?
    • @eugeney:我想你为此打开了一个新问题,而不是评论。您可以随时链接到此链接以节省一些打字时间。
    • 这对 SQL 本身有帮助,但对发帖人的 DBIx::Class 没有帮助,这就是他的问题所在。
    • @Wokka:我确实为这个问题提供了一个解决方案,看起来问题的作者很满意。我什至研究了 Perl 文档并指出了一个缺点。
    【解决方案2】:

    find_or_create 的这种实现应该防止竞争条件,如 OP 中所述:

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

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

    【讨论】:

    • 这颠倒了逻辑。但是现在您有一个很小的时间段,可以在其中删除条目 - 在这种情况下,逻辑将失败。尝试先写比尝试阅读更昂贵。因此,如果重复非常少见,这只是一种改进。无论哪种方式,冲突都应该很少见,因为时间间隔很小。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多