2017-10-10 16:52:02 159次浏览 1条回答 0 悬赏 10 金钱

困扰了我很久的问题:我在Labclass模型类中加了beforeSave,如果记录符合设定的条件,返回true,否则返回false。应用实际中,发现有些明明不符合过滤条件的记录,也进入了数据库,即出现了“漏网之鱼”
今天写了个控制台测试程序,如下:

console\controllers\ModifyDbController.php

public function actionTestLabclassUpdate($reset = 0)
{
    $id = 18513; $idClassRoom = 39; $idLabRoom = 36;
    if ($reset == 1) {
        $model = \frontend\models\Labclass::findOne($id);
        $model->room_id = $idClassRoom;
        $model->save();
    } else {
        $model1 = \frontend\models\Labclass::findOne($id);
        $model2 = \frontend\models\Labclass::findOne($id);
        $model1->room_id = $idLabRoom;
        $model2->room_id = $idClassRoom;
        $model2->save();
        $model1->save();
    }
    $labclass = \frontend\models\Labclass::findOne($id);
    //var_dump($labclass->attributes);
    echo "\n".$labclass->room_id."\t".$labclass->room->name."\n\n";
}

然后控制台执行,结果

x201@x201-pc:~/PhpstormProjects/wxinfo$ ./yii modify-db/test-labclass-update

36	多媒体机房二

x201@x201-pc:~/PhpstormProjects/wxinfo$ ./yii modify-db/test-labclass-update

39	长安教室(通用名)

x201@x201-pc:~/PhpstormProjects/wxinfo$ ./yii modify-db/test-labclass-update

39	长安教室(通用名)

x201@x201-pc:~/PhpstormProjects/wxinfo$ ./yii modify-db/test-labclass-update

39	长安教室(通用名)

x201@x201-pc:~/PhpstormProjects/wxinfo$ ./yii modify-db/test-labclass-update

39	长安教室(通用名)

x201@x201-pc:~/PhpstormProjects/wxinfo$ ./yii modify-db/test-labclass-update

39	长安教室(通用名)

x201@x201-pc:~/PhpstormProjects/wxinfo$ ./yii modify-db/test-labclass-update

39	长安教室(通用名)

x201@x201-pc:~/PhpstormProjects/wxinfo$ ./yii modify-db/test-labclass-update

39	长安教室(通用名)

x201@x201-pc:~/PhpstormProjects/wxinfo$ ./yii modify-db/test-labclass-update

36	多媒体机房二

以上是连续,但间隔时间不等的执行(比较快连续执行是 39 那一行,36那行记录本应该被 beforeSave 拦截的)

个人猜测,在ActiveRecord的beforeSave等执行流程中,会出现脏写——这个问题不太好分析具体流程(我的开发环境是Deepin Linux 2015.4.1 x64,apache2.4.18,php 7.0.6,10.1.13-MariaDB,和环境应该关系不大,因为部署环境是Windows Server 2008,apache + mysql)

问题:在ActiveRecord中,如何实现锁定一条记录?

补充于 2017-10-11 16:08

采用事务或者自己设计锁(beforeSave lock,afterSave unlock)也没用,经过试验,发现似乎关联查询存在“迟滞”造成脏读:

beforeSave内

if (parent::beforeSave($insert)) {
  // many code ...
  $conflictResult = $this->isConflictExist();
  if(array_key_exists(self::SAVE_TYPE_ERROR, $conflictResult)) {
       return false;
  }
  // other code ...
  return true;
} else {
  return false;
}

isConflictExist() 内

// some code ...
if($conflictType == self::CONFLICT_TYPE_ROOM && $this->room->devicenumber == 0 &&
   $this->room->category == Room::CATEGORY_CLASSROOM) {
   echo "\nclassroom! no conflict!!room_id = {$this->room_id}, room->id = {$this->room->id}";
   continue;
} else {
  //many code ...
}

room关系(Labclass外键room_id)

    public function getRoom()
    {
        return $this->hasOne(Room::className(), ['id' => 'room_id']);
    }

测试代码(else部分)

            $count = 10;
            while ($count--) {
                $model1 = \frontend\models\Labclass::findOne($id);
                $model2 = \frontend\models\Labclass::findOne($id);
                $model1->room_id = $idLabRoom;
                $model2->room_id = $idClassRoom;
                //\frontend\models\Labclass::getDb()->transaction(function ($db) use ($model1) {
                    echo "\nmodel1: ".($model1->save() ? 1 : 0);
                //});
                //sleep(2);
                //\frontend\models\Labclass::getDb()->transaction(function ($db) use ($model2) {
                    echo "\nmodel2: ".($model2->save() ? 1 : 0);
                //});
                $labclass = \frontend\models\Labclass::findOne($id);
                //var_dump($labclass->attributes);
                echo "\nUpdate 1--".$labclass->room_id."\t".$labclass->room->name;
                sleep(rand(120, 180));
           }

输出结果:
false at 0.18354200 1507697371
model1: 0
classroom! no conflict!!room_id = 39, room->id = 39
true at 0.19770900 1507697371
model2: 1
Update 1--39 长安教室(通用名)
classroom! no conflict!!room_id = 36, room->id = 39
true at 0.22769700 1507697516
model1: 1
classroom! no conflict!!room_id = 39, room->id = 39
true at 0.25091500 1507697516
model2: 1
Update 1--36 多媒体机房二
false at 0.29225100 1507697646
model1: 0
classroom! no conflict!!room_id = 39, room->id = 39
true at 0.30175200 1507697646
model2: 1
Update 1--39 长安教室(通用名)
false at 0.33835600 1507697811
model1: 0
classroom! no conflict!!room_id = 39, room->id = 39
true at 0.35387100 1507697811
model2: 1
Update 1--39 长安教室(通用名)
classroom! no conflict!!room_id = 36, room->id = 39
true at 0.38394400 1507697931
model1: 1
classroom! no conflict!!room_id = 39, room->id = 39
true at 0.40254100 1507697931
model2: 1
Update 1--36 多媒体机房二
false at 0.44047400 1507698111
model1: 0
classroom! no conflict!!room_id = 39, room->id = 39
true at 0.45133800 1507698111
model2: 1
Update 1--39 长安教室(通用名)
classroom! no conflict!!room_id = 36, room->id = 39
true at 0.48433500 1507698235
model1: 1
classroom! no conflict!!room_id = 39, room->id = 39
true at 0.50019700 1507698235
model2: 1
Update 1--36 多媒体机房二
false at 0.52970100 1507698386
model1: 0
classroom! no conflict!!room_id = 39, room->id = 39
true at 0.53908300 1507698386
model2: 1
Update 1--39 长安教室(通用名)
classroom! no conflict!!room_id = 36, room->id = 39
true at 0.57195000 1507698543
model1: 1
classroom! no conflict!!room_id = 39, room->id = 39
true at 0.58906600 1507698543
model2: 1
Update 1--36 多媒体机房二
false at 0.62750300 1507698672
model1: 0
classroom! no conflict!!room_id = 39, room->id = 39
true at 0.63890300 1507698672
model2: 1
Update 1--39 长安教室(通用名)

对于$this这个模型实例,同一个位置的代码 $this->room_id 获得的值和 $this->room->id 获得的值居然有时候不同!!有没有谁了解背后的机制??

补充于 2017-10-12 13:42

还是自己来了结此事,解决方案是,在用 $this->room 这个“关系属性”之前 unset($this->room),从而迫使用到 $this->room 的时候重新查询,具体情况看权威指南 http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#relational-data 中 Accessing Relational Data 一节:
AR类定义了 getXyz() 关系,那么 $model->xyz 返回的是模型实例或者模型实例构成的数组,$model->getXyz() 返回的是 AQ 类的实例,后者代表着一个查询,前者是查询后的实例结果,如果$model->xyz连续用了两次,那么,实际执行一次查询,后一次肯定是用了“缓存”的结果。在我的例子中,算不上连续用了两次,但属于“很接近”,具体不清楚是如何“脏读”的(试验中快速连续时,反而后续结果都是正确的,隔2分钟左右才会出现问题)。

您需要登录后才可以回答。登录 | 立即注册
sjg20010414
见习主管

sjg20010414

注册时间:2013-08-16
最后登录:2017-10-20
在线时长:2小时40分

热门问题