深入理解Yii2.0乐观锁与悲观锁的原理与使用

时间:2017-08-11

虽然悲观锁在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;
  }
}

上面的代码对比乐观锁,主要不同点在于:

  1. 新增加了一个加锁方法,一个获取锁定最大时长的方法。
  2. 保存时不再是把标识字段+1,而是把标识字段置0。
  • 共4页:
  • 上一页
  • 3/4
  • 下一页
  • 上一篇:php判断str字符串是否是xml格式数据的方法示例 下一篇:PHP编程实现微信企业向用户付款的方法示例

    相关文章

    最新文章