ActiveRecord如何在beforeSave执行前锁定单条记录 [ 2.0 版本 ]
困扰了我很久的问题:我在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中,如何实现锁定一条记录?
采用事务或者自己设计锁(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 获得的值居然有时候不同!!有没有谁了解背后的机制??
还是自己来了结此事,解决方案是,在用 $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
最后登录:2021-11-08
在线时长:2小时49分
- 粉丝1
- 金钱80
- 威望10
- 积分200