Stories

Detail Return Return

Yii 實現樂觀鎖和悲觀鎖 - Stories Detail

一:在Yii中實現樂觀鎖

樂觀鎖(optimistic locking)表現出大膽、務實的態度。使用樂觀鎖的前提是, 實際應用當中,發生衝突的概率比較低。他的設計和實現直接而簡潔。 目前Web應用中,樂觀鎖的使用佔有絕對優勢。因此在Yii為ActiveReocrd樂觀鎖支持

1:在yii中實現樂觀鎖步驟、

1):給需要加鎖的表添加一個字段,用於表示版本號,這裏我一般選手version字段作為版本號字段,注意,如果你需要加鎖的表已經生成Model了,那麼對應表的Model要將你添加的版本號字段(version)信息加入Model

2):在更新表中字段時,使用 try ... catch 看看是否能捕獲一個 yii\db\StaleObjectException 異常,如果捕捉到yii\db\StaleObjectException 異常,説明在本次修改這個記錄的過程中, 該記錄已經被修改過了,作出相應提示

2:Yii中實現樂觀鎖

1):在yii中聲明指定字段為版本號

版本號是實現樂觀鎖的根本所在。所以第一步,我們要告訴Yii,哪個字段是版本號字段,聲明版本號的方法由yii\db\BaseActiveRecord(vendor/yiisoft/yii2/db/BaseActiveRecord)中的optimisticLock方法負責

public function optimisticLock()
{
    return null;
}

這個方法返回 null ,表示不使用樂觀鎖,如果我們需要使用樂觀鎖的話,我們需要在我們的需要加鎖的表的Model中重載optimisticLock方法

public function optimisticLock()
{
    return 'version';
}

如上説明當前的ActiveRecord中,有一個 version 字段,可以為樂觀鎖所用

2:實現樂觀鎖

我們在Model中設置了版本號後,這時候我們的更新和刪除都是樂觀鎖操作了,與正常操作數據庫的方式一致

try {
    $crowd = Crowd::findOne(['crowd_id' => 12]);
    $crowd->status = 1;
    $crowd->save();
} catch (\Exception $e) {
    return false;
}

在更新過程中,我們會調用到 yii\db\BaseActiveRecord::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;
        }
        // 把原來ActiveRecord的主鍵作為等下更新記錄的條件,也就是説,等下更新的,最多隻有1個記錄。
        $condition = $this->getOldPrimaryKey(true);
        // 獲取版本號字段的字段名,比如 version
        $lock = $this->optimisticLock();
        // 如果 optimisticLock() 返回的是 null,那麼,不啓用樂觀鎖。
        if ($lock !== null) {
        // 這裏的 $this->$lock ,就是 $this->version 的意思; 這裏把 version+1 作為要更新的字段之一。
            $values[$lock] = $this->$lock + 1;
            // 這裏把舊的版本號作為更新的另一個條件
            $condition[$lock] = $this->$lock;
        }
        
        $rows = static::updateAll($values, $condition);
// 如果已經啓用了樂觀鎖,但是卻沒有完成更新,或者更新的記錄數為0;
    // 那就説明是由於 version 不匹配,記錄被修改過了,於是拋出異常。
        if ($lock !== null && !$rows) {
            throw new StaleObjectException('The object being updated is outdated.');
        }
        if (isset($values[$lock])) {
            $this->$lock = $values[$lock];
        }
        $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;
    }

在刪除過程中,我們會調用到 yii\db\BaseActiveRecord::delete()方法,此方法裏面就具有處理樂觀鎖的代碼

public function delete()
    {
        $result = false;
        if ($this->beforeDelete()) {
            // 刪除的SQL語句中,WHERE部分是主鍵
            $condition = $this->getOldPrimaryKey(true);
            // 獲取版本號字段的字段名,比如 version
            $lock = $this->optimisticLock();
            // 如果啓用樂觀鎖,那麼WHERE部分再加一個條件,版本號
            if ($lock !== null) {
                $condition[$lock] = $this->$lock;
            }
            $result = static::deleteAll($condition);
            if ($lock !== null && !$result) {
                throw new StaleObjectException('The object being deleted is outdated.');
            }
            $this->_oldAttributes = null;
            $this->afterDelete();
        }
        return $result;
    }

如上我們就知道了,在yii中已經有了樂觀鎖相關的代碼了,我們只需要在Model中設置一個版本號字段即可

二:在Yii中實現悲觀鎖

正如其名字,悲觀鎖(pessimistic locking)體現了一種謹慎的處事態度

1:在yii中實現悲觀鎖的步驟

1):在對任意記錄進行修改前,先嚐試為該記錄加上鎖

2):如果加鎖失敗,説明該記錄正在被修改,那麼當前查詢可能要等待或者拋出異常

3):如果成功加鎖,那麼就可以對記錄做修改,事務完成後就會解鎖了

2:yii中悲觀鎖實現

使用select.....for update實現悲觀鎖,簡單示例如下:

$transaction = Yii::$app->db->beginTransaction();
try{
    //查詢id為12的這條數據並且鎖定
    $sql = "select * from ubo_crowd where crowd_id = 12 for update";
    $crowd = Yii::$app->db->createCommand($sql)->queryOne();
    //更新數據
    $crowd1 = Crowd::findOne(['crowd_id' => $crowd['crowd_id']]);
    $crowd1->sort += 1;
    if($crowd1->save()){
        $transaction->commit();
    }
}catch(Exception $e){
    $transaction->rollBack();
}
user avatar ljc1212 Avatar mengxiang_592395ab95632 Avatar cloud11y Avatar
Favorites 3 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.