【问题标题】:Yii2 database transaction behaviour to support repeatable readYii2 数据库事务行为支持可重复读
【发布时间】:2020-06-06 00:40:04
【问题描述】:

我有以下问题。我有一个 Web 应用程序(用 php Yii2 编写),其中多个 post 请求预计会在很短的时间内到达应用程序服务器。业务逻辑应该非常严格,这意味着只有第一个请求的数据应该插入到 MySQL 表中,其余的都应该忽略。 客户端在发布请求中同时发送父记录和最新子记录的 ID。 我就是这样使用 Yii 的 db 事务的。

$transaction = Yii::$app->db->beginTransaction();
$parent = ObjectParent::findOne(Yii::$app->request->post('parent_id')));
$latest_child = ObjectChild::findOne(Yii::$app->request->post('latest_child_id')));

if($parent->latest_child_id == $latest_child->id) {
    try{
        $new_child = $latest_child->createNewChild();
        $parent->setLatestChild($new_child->id);
        $transaction->commit();
    } catch{
        $transaction->rollback();
    }
}

如果请求按顺序收到,则第二个请求将被忽略,因为最新的子记录的 id 与来自客户端的不匹配。但我的问题是,数据库中插入了多行。数据库的隔离级别是 REPEATABLE READ,它应该确保(据我所知)在事务中读取的行保证在提交发生之前不会更改。如果这是真的,那就不是问题,因为它会使第二笔交易“中断”。 问题可能是,Yii 可能不使用或不知道这些数据库锁,所以不知道记录已经是事务的一部分,并根据对象的当前状态进行验证。数据库当然对验证规则一无所知,所以从它的角度来看也很好。

我解决这个问题的想法:

  1. 也将 yii 事务显式设置为 REPEATABLE READ。这可能会改变它的行为。我怀疑,因为根据文档,没有明确定义它,它使用数据库默认值(REPEATABLE READ)。
  2. 把验证逻辑放在后面一点,更接近提交,在 $parent->setLatestChildId($new_child->id); 之后 我不知道这是否是 100% 的解决方案,所以我不想开始重写测试过的代码。注意,上面的骨架代码只是原版的简化版。

  3. 使用数据库触发器解决整个问题,因此它会绕过应用程序上下文。

请让我知道在这些情况下的最佳做法是什么。不幸的是,我在这些并发问题上经验不足,而且很难测试和模拟并发请求。 谢谢

【问题讨论】:

    标签: mysql concurrency yii2 transactions transaction-isolation


    【解决方案1】:

    可重复只读确保如果您读取事务中的行,重新读取这些行会得到相同的结果。另一笔交易可能会改变结果。

    要对它们进行一些锁定,可以执行以下操作:

    SELECT ... [LOCK IN SHARE MODE|FOR UPDATE]
    

    但是,为了确保插入的父/子是唯一的,我建议 (parent_id,child_id) 作为表中的唯一键或主键,这样重复插入将生成重复键异常。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-13
      • 1970-01-01
      • 2021-09-30
      • 2011-02-10
      • 2015-05-04
      相关资源
      最近更新 更多