虽然悲观锁在Web应用上存在诸多不足,实现悲观锁也需要解决各种麻烦。但是, 当用户提出他就是要用悲观锁时,牙口再不好的码农,就是咬碎牙也是要啃下这块骨头来。
对于一个典型的Web应用而言,这里提供个人常用的方法来实现悲观锁。
首先,在要锁定的表里,加一个字段如 locked_at ,表示当前记录被锁定时的时间, 当为 0 时,表示该记录未被锁定,或者认为这是1970年时加的锁。
当要修改某个记录时,先看看当前时间与 locked_at 字段相差是否超过预定的一个时长T,比如 30 min ,1 h 之类的。
如果没超过,说明该记录有人正在修改,我们暂时不能打开(读取)他来修改。 否则,说明可以修改,我们先将当前时间戳保存到该记录的 locked_at 字段。 那么之后的时长T内如果有人要来改这个记录,他会由于加锁失败而无法读取, 从而无法修改。
我们在完成修改后,即将保存时,要比对现在的 locked_at 。只有在 locked_at 一致时,才认为刚刚是我们加的锁,我们才可以保存。 否则,说明在我们加锁后,又有人加了锁正在修改, 或者已经完成了修改,使得 locked_at 归 0。
这种情况主要是由于我们的修改时长过长,超过了预定的T。原先的加锁自动解开, 其他用户可以在我们加锁时刻再过T之后,重新加上自己的锁。换句话说, 此时悲观锁退化为乐观锁。
大致的原理性代码如下:
// 悲观锁AR基类,需要使用悲观锁的AR可以由此派生
class PLockAR extends \yii\db\BaseActiveRecord {
// 声明悲观锁使用的标记字段,作用类似于 optimisticLock() 方法
public function pesstimisticLock() {
return null;
}
// 定义锁定的最大时长,超过该时长后,自动解锁。
public function maxLockTime() {
return 0;
}
// 尝试加锁,加锁成功则返回true
public function lock() {
$lock = $this->pesstimisticLock();
$now = time();
$values = [$lock => $now];
// 以下2句,更新条件为主键,且上次锁定时间距现在超过规定时长
$condition = $this->getOldPrimaryKey(true);
$condition[] = ['<', $lock, $now - $this->maxLockTime()];
$rows = $this->updateAll($values, $condition);
// 加锁失败,返回 false
if (! $rows) {
return false;
}
return true;
}
// 重载updateInternal()
protected function updateInternal($attributes = null)
{
// 这些与原来代码一样
if (!$this->beforeSave(false)) {
return false;
}
$values = $this->getDirtyAttributes($attributes);
if (empty($values)) {
$this->afterSave(false, $values);
return 0;
}
$condition = $this->getOldPrimaryKey(true);
// 改为获取悲观锁标识字段
$lock = $this->pesstimisticLock();
// 如果 $lock 为 null,那么,不启用悲观锁。
if ($lock !== null) {
// 等下保存时,要把标识字段置0
$values[$lock] = 0;
// 这里把原来的标识字段值作为更新的另一个条件
$condition[$lock] = $this->$lock;
}
$rows = $this->updateAll($values, $condition);
// 如果已经启用了悲观锁,但是却没有完成更新,或者更新的记录数为0;
// 那就说明之前的加锁已经自动失效了,记录正在被修改,
// 或者已经完成修改,于是抛出异常。
if ($lock !== null && !$rows) {
throw new StaleObjectException('The object being updated is outdated.');
}
$changedAttributes = [];
foreach ($values as $name => $value) {
$changedAttributes[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
$this->_oldAttributes[$name] = $value;
}
$this->afterSave(false, $changedAttributes);
return $rows;
}
}
上面的代码对比乐观锁,主要不同点在于:
yii2组件之下拉框带搜索功能的示例代码(yii-select2)本篇文章主要介绍了yii2组件之下拉框带搜索功能的示例代码(yii-select2),具有一定的参考价值,有兴趣的可以了解
yii2中使用webuploader实现图片上传的实战项目本篇文章主要主要介绍了yii2中使用webuploader实现图片上传的实战项目,具有一定的参考价值,有兴趣的同学可以了解
浅谈Yii乐观锁的使用及原理本篇文章主要介绍了浅谈Yii2 乐观锁与悲观锁原理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟
修改yii2.0用户登录使用的user表为其它的表实现方法(推荐)下面小编就为大家带来一篇修改yii2.0用户登录使用的user表为其它的表实现方法(推荐)。小编觉得挺不错的,现在就分
如何修改yii2.0自带的user表为其它的表因为某种原因,不想用yii自带的user表,想用自己建的admin数据库表,怎么修改呢?下面小编给大家介绍下修改yii2.0自
Yii2第三方类库插件Imagine的安装和使用本篇文章主要介绍了Yii2第三方类库插件Imagine的安装和使用,具有一定的参考价值,感兴趣的小伙伴们可以参考一下