欧美一级特黄大片做受成人-亚洲成人一区二区电影-激情熟女一区二区三区-日韩专区欧美专区国产专区

Yii2.0樂觀鎖與悲觀鎖怎么用-創(chuàng)新互聯(lián)

這篇文章將為大家詳細(xì)講解有關(guān)Yii2.0樂觀鎖與悲觀鎖怎么用,小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

為萊陽等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計(jì)制作服務(wù),及萊陽網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為網(wǎng)站建設(shè)、成都做網(wǎng)站、萊陽網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠(yuǎn)!

Web應(yīng)用往往面臨多用戶環(huán)境,這種情況下的并發(fā)寫入控制, 幾乎成為每個(gè)開發(fā)人員都必須掌握的一項(xiàng)技能。

在并發(fā)環(huán)境下,有可能會(huì)出現(xiàn)臟讀(Dirty Read)、不可重復(fù)讀(Unrepeatable Read)、 幻讀(Phantom Read)、更新丟失(Lost update)等情況。具體的表現(xiàn)可以自行搜索。

為了應(yīng)對(duì)這些問題,主流數(shù)據(jù)庫都提供了鎖機(jī)制,并引入了事務(wù)隔離級(jí)別的概念。 這里我們都不作解釋了,拿這些關(guān)鍵詞一搜,網(wǎng)上大把大把的。

但是,就于具體開發(fā)過程而言,一般分為悲觀鎖和樂觀鎖兩種方式來解決并發(fā)沖突問題。

樂觀鎖

樂觀鎖(optimistic locking)表現(xiàn)出大膽、務(wù)實(shí)的態(tài)度。使用樂觀鎖的前提是, 實(shí)際應(yīng)用當(dāng)中,發(fā)生沖突的概率比較低。他的設(shè)計(jì)和實(shí)現(xiàn)直接而簡潔。 目前Web應(yīng)用中,樂觀鎖的使用占有絕對(duì)優(yōu)勢(shì)。

因此,Yii也為ActiveReocrd提供了樂觀鎖支持。

根據(jù)Yii的官方文檔,使用樂觀鎖,總共分4步:

  • 為需要加鎖的表增加一個(gè)字段,用于表示版本號(hào)。 當(dāng)然相應(yīng)的Model也要為該字段的加入,作出適當(dāng)調(diào)整。比如, rules() 中要加入該字段。

  • 重載 yii\db\ActiveRecord::optimisticLock() 方法,返回上一步中的字段名。

  • 在記錄的修改頁面表單中,加入一個(gè) <input type="hidden"> 用于暫存讀取時(shí)的記錄的版本號(hào)。

  • 在保存代碼的地方,使用 try ... catch 看看是否能捕獲一個(gè) yii\db\StaleObjectException 異常。如果是,說明在本次修改這個(gè)記錄的過程中, 該記錄已經(jīng)被修改過了。簡單應(yīng)對(duì)的話,可以作出相應(yīng)提示。智能點(diǎn)的話, 可以合并不沖突的修改,或者顯示一個(gè)diff頁面。

從本質(zhì)上來講,樂觀鎖并沒有像悲觀鎖那樣使用數(shù)據(jù)庫的鎖機(jī)制。 樂觀鎖通過在表中增加一個(gè)計(jì)數(shù)字段,來表示當(dāng)前記錄被修改的次數(shù)(版本號(hào))。

然后在更新、刪除前通過比對(duì)版本號(hào)來實(shí)現(xiàn)樂觀鎖。

聲明版本號(hào)字段

版本號(hào)是實(shí)現(xiàn)樂觀鎖的根本所在。所以第一步,我們要告訴Yii,哪個(gè)字段是版本號(hào)字段。 這個(gè)由 yii\db\BaseActiveRecord 負(fù)責(zé):

public function optimisticLock()
{
  return null;
}

這個(gè)方法返回 null ,表示不使用樂觀鎖。那么我們的Model中,要對(duì)此進(jìn)行重載。 返回一個(gè)字符串,表示我們用于標(biāo)識(shí)版本號(hào)的字段。比如可以這樣:

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

說明當(dāng)前的ActiveRecord中,有一個(gè) ver 字段,可以為樂觀鎖所用。 那么Yii具體是如何借助這個(gè) ver 字段實(shí)現(xiàn)樂觀鎖的呢?

更新過程

具體來講,使用樂觀鎖之后的更新過程,就是這么一個(gè)流程:

  1. 讀取要更新的記錄。

  2. 對(duì)記錄按照用戶的意愿進(jìn)行修改。當(dāng)然,這個(gè)時(shí)候不會(huì)修改 ver 字段。 這個(gè)字段對(duì)用戶是沒意義的。

  3. 在保存記錄前,再次讀取這個(gè)記錄的 ver 字段,與之前讀取的值進(jìn)行比對(duì)。

  4. 如果 ver 不同,說明在用戶修改過程中,這個(gè)記錄被別人改動(dòng)過了。那么, 我們要給出提示。

  5. 如果 ver 相同,說明這個(gè)記錄未被修改過。那么,對(duì) ver +1, 并保存這個(gè)記錄。這樣子就完成了記錄的更新。同時(shí),該記錄的版本號(hào)也加了1。

由于ActiveRecord的更新過程最終都需要調(diào)用 yii\db\BaseActiveRecord::updateInteranl(),理所當(dāng)然地,處理樂觀鎖的代碼, 也就隱藏在這個(gè)方法中:


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個(gè)記錄。
  $condition = $this->getOldPrimaryKey(true);

  // 獲取版本號(hào)字段的字段名,比如 ver
  $lock = $this->optimisticLock();

  // 如果 optimisticLock() 返回的是 null,那么,不啟用樂觀鎖。
  if ($lock !== null) {
    // 這里的 $this->$lock ,就是 $this->ver 的意思;
    // 這里把 ver+1 作為要更新的字段之一。
    $values[$lock] = $this->$lock + 1;

    // 這里把舊的版本號(hào)作為更新的另一個(gè)條件
    $condition[$lock] = $this->$lock;
  }
  $rows = $this->updateAll($values, $condition);

  // 如果已經(jīng)啟用了樂觀鎖,但是卻沒有完成更新,或者更新的記錄數(shù)為0;
  // 那就說明是由于 ver 不匹配,記錄被修改過了,于是拋出異常。
  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. 當(dāng) optimisticLock() 返回 null 時(shí),樂觀鎖不會(huì)被啟用。

  2. 版本號(hào)只增不減。

  3. 通過樂觀鎖的條件有2個(gè),一是主鍵要存在,二是要能夠完成更新。

  4. 當(dāng)啟用樂觀鎖后,只有下列兩種情況會(huì)拋出 StaleObjectException 異常:

    1. 當(dāng)記錄在被別人刪除后,由于主鍵已經(jīng)不存在,更新失敗。

    2. 版本號(hào)已經(jīng)變更,不滿足更新的第二個(gè)條件。

刪除過程

與更新過程相比,刪除過程的樂觀鎖,更簡單,更好理解。代碼仍在 yii\db\BaseActiveRecord 中:

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

比起更新過程,刪除過程確實(shí)要簡單得多。的區(qū)別就是省去了版本號(hào)+1的步驟。 都要?jiǎng)h除了,版本號(hào)+1有什么意義?

樂觀鎖失效

樂觀鎖存在失效的情況,屬小概率事件,需要多個(gè)條件共同配合才會(huì)出現(xiàn)。如:

  1. 應(yīng)用采用自己的策略管理主鍵ID。如,常見的取當(dāng)前ID字段的較大值+1作為新ID。

  2. 版本號(hào)字段 ver 默認(rèn)值為 0 。

  3. 用戶A讀取了某個(gè)記錄準(zhǔn)備修改它。該記錄正好是ID較大的記錄,且之前沒被修改過, ver 為默認(rèn)值 0。

  4. 在用戶A讀取完成后,用戶B恰好刪除了該記錄。之后,用戶C又插入了一個(gè)新記錄。

  5. 此時(shí),陰差陽錯(cuò)的,新插入的記錄的ID與用戶A讀取的記錄的ID是一致的, 而版本號(hào)兩者又都是默認(rèn)值 0。

  6. 用戶A在用戶C操作完成后,修改完成記錄并保存。由于ID、ver均可以匹配上, 因此用戶A成功保存。但是,卻把用戶C插入的記錄覆蓋掉了。

樂觀鎖此時(shí)的失效,根本原因在于應(yīng)用所使用的主鍵ID管理策略, 正好與樂觀鎖存在極小程度上的不兼容。

兩者分開來看,都是沒問題的。組合到一起之后,大致看去好像也沒問題。 但是bug之所以成為bug,坑之所以能夠坑死人,正是由于其隱蔽性。

對(duì)此,也有一些意見提出來,使用時(shí)間戳作為版本號(hào)字段,就可以避免這個(gè)問題。 但是,時(shí)間戳的話,如果精度不夠,如毫秒級(jí)別,那么在高并發(fā),或者非常湊巧情況下, 仍有失效的可能。而如果使用高精度時(shí)間戳的話,成本又太高。

使用時(shí)間戳,可靠性并不比使用整型好。問題還是要回到使用嚴(yán)謹(jǐn)?shù)闹麈I成生策略上來。

悲觀鎖

正如其名字,悲觀鎖(pessimistic locking)體現(xiàn)了一種謹(jǐn)慎的處事態(tài)度。其流程如下:

  1. 在對(duì)任意記錄進(jìn)行修改前,先嘗試為該記錄加上排他鎖(exclusive locking)。

  2. 如果加鎖失敗,說明該記錄正在被修改,那么當(dāng)前查詢可能要等待或者拋出異常。 具體響應(yīng)方式由開發(fā)者根據(jù)實(shí)際需要決定。

  3. 如果成功加鎖,那么就可以對(duì)記錄做修改,事務(wù)完成后就會(huì)解鎖了。

  4. 其間如果有其他對(duì)該記錄做修改或加排他鎖的操作,都會(huì)等待我們解鎖或直接拋出異常。

悲觀鎖確實(shí)很嚴(yán)謹(jǐn),有效保證了數(shù)據(jù)的一致性,在C/S應(yīng)用上有諸多成熟方案。 但是他的缺點(diǎn)與優(yōu)點(diǎn)一樣的明顯:

  1. 悲觀鎖適用于可靠的持續(xù)性連接,諸如C/S應(yīng)用。 對(duì)于Web應(yīng)用的HTTP連接,先天不適用。

  2. 鎖的使用意味著性能的損耗,在高并發(fā)、鎖定持續(xù)時(shí)間長的情況下,尤其嚴(yán)重。 Web應(yīng)用的性能瓶頸多在數(shù)據(jù)庫處,使用悲觀鎖,進(jìn)一步收緊了瓶頸。

  3. 非正常中止情況下的解鎖機(jī)制,設(shè)計(jì)和實(shí)現(xiàn)起來很麻煩,成本還很高。

  4. 不夠嚴(yán)謹(jǐn)?shù)脑O(shè)計(jì)下,可能產(chǎn)生莫名其妙的,不易被發(fā)現(xiàn)的, 讓人頭疼到想把鍵盤一巴掌碎的死鎖問題。

總體來看,悲觀鎖不大適應(yīng)于Web應(yīng)用,Yii團(tuán)隊(duì)也認(rèn)為悲觀鎖的實(shí)現(xiàn)過于麻煩, 因此,ActiveRecord也沒有提供悲觀鎖。

作為Yii的構(gòu)成基因之一的Ruby on rails,他的ActiveReocrd模型,倒是提供了悲觀鎖, 但是使用起來也很麻煩。

悲觀鎖的實(shí)現(xiàn)

雖然悲觀鎖在Web應(yīng)用上存在諸多不足,實(shí)現(xiàn)悲觀鎖也需要解決各種麻煩。但是, 當(dāng)用戶提出他就是要用悲觀鎖時(shí),牙口再不好的碼農(nóng),就是咬碎牙也是要啃下這塊骨頭來。

對(duì)于一個(gè)典型的Web應(yīng)用而言,這里提供個(gè)人常用的方法來實(shí)現(xiàn)悲觀鎖。

首先,在要鎖定的表里,加一個(gè)字段如 locked_at ,表示當(dāng)前記錄被鎖定時(shí)的時(shí)間, 當(dāng)為 0 時(shí),表示該記錄未被鎖定,或者認(rèn)為這是1970年時(shí)加的鎖。

當(dāng)要修改某個(gè)記錄時(shí),先看看當(dāng)前時(shí)間與 locked_at 字段相差是否超過預(yù)定的一個(gè)時(shí)長T,比如 30 min ,1 h 之類的。

如果沒超過,說明該記錄有人正在修改,我們暫時(shí)不能打開(讀?。┧麃硇薷?。 否則,說明可以修改,我們先將當(dāng)前時(shí)間戳保存到該記錄的 locked_at 字段。 那么之后的時(shí)長T內(nèi)如果有人要來改這個(gè)記錄,他會(huì)由于加鎖失敗而無法讀取, 從而無法修改。

我們?cè)谕瓿尚薷暮螅磳⒈4鏁r(shí),要比對(duì)現(xiàn)在的 locked_at 。只有在 locked_at 一致時(shí),才認(rèn)為剛剛是我們加的鎖,我們才可以保存。 否則,說明在我們加鎖后,又有人加了鎖正在修改, 或者已經(jīng)完成了修改,使得 locked_at 歸 0。

這種情況主要是由于我們的修改時(shí)長過長,超過了預(yù)定的T。原先的加鎖自動(dòng)解開, 其他用戶可以在我們加鎖時(shí)刻再過T之后,重新加上自己的鎖。換句話說, 此時(shí)悲觀鎖退化為樂觀鎖。

大致的原理性代碼如下:


// 悲觀鎖AR基類,需要使用悲觀鎖的AR可以由此派生
class PLockAR extends \yii\db\BaseActiveRecord {
  // 聲明悲觀鎖使用的標(biāo)記字段,作用類似于 optimisticLock() 方法
  public function pesstimisticLock() {
    return null;
  }

  // 定義鎖定的較大時(shí)長,超過該時(shí)長后,自動(dòng)解鎖。
  public function maxLockTime() {
    return 0;
  }

  // 嘗試加鎖,加鎖成功則返回true
  public function lock() {
    $lock = $this->pesstimisticLock();
    $now = time();
    $values = [$lock => $now];
    // 以下2句,更新條件為主鍵,且上次鎖定時(shí)間距現(xiàn)在超過規(guī)定時(shí)長
    $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);

    // 改為獲取悲觀鎖標(biāo)識(shí)字段
    $lock = $this->pesstimisticLock();

    // 如果 $lock 為 null,那么,不啟用悲觀鎖。
    if ($lock !== null) {
      // 等下保存時(shí),要把標(biāo)識(shí)字段置0
      $values[$lock] = 0;

      // 這里把原來的標(biāo)識(shí)字段值作為更新的另一個(gè)條件
      $condition[$lock] = $this->$lock;
    }
    $rows = $this->updateAll($values, $condition);

    // 如果已經(jīng)啟用了悲觀鎖,但是卻沒有完成更新,或者更新的記錄數(shù)為0;
    // 那就說明之前的加鎖已經(jīng)自動(dòng)失效了,記錄正在被修改,
    // 或者已經(jīng)完成修改,于是拋出異常。
    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;
  }
}

上面的代碼對(duì)比樂觀鎖,主要不同點(diǎn)在于:

  1. 新增加了一個(gè)加鎖方法,一個(gè)獲取鎖定較大時(shí)長的方法。

  2. 保存時(shí)不再是把標(biāo)識(shí)字段+1,而是把標(biāo)識(shí)字段置0。

在具體使用方法上,可以參照以下代碼:

// 從PLockAR派生模型類
class Post extends PLockAR {
  // 重載定義悲觀鎖標(biāo)識(shí)字段,如 locked_at
  public function pesstimisticLock() {
    return 'locked_at';
  }
  // 重載定義較大鎖定時(shí)長,如1小時(shí)
  public function maxLockTime() {
    return 3600000;
  }
}

// 修改前要嘗試加鎖
class SectionController extends Controller {
  public function actionUpdate($id)
  {
    $model = $this->findModel($id);

    if ($model->load(Yii::$app->request->post()) && $model->save()) {
      return $this->redirect(['view', 'id' => $model->id]);
    } else {
      // 加入一個(gè)加鎖的判斷
      if (!$model->lock()) {
        // 加鎖失敗
        // ... ...
      }
      return $this->render('update', [
        'model' => $model,
      ]);
    }
  }
}

上述方法實(shí)現(xiàn)的悲觀鎖,避免了使用數(shù)據(jù)庫自身的鎖機(jī)制,契合Web應(yīng)用的特點(diǎn), 具有一定的適用性,但是也存在一定的缺陷:

  1. 最長允許鎖定時(shí)長會(huì)帶來一定的副作用。時(shí)間定得長了,可能要等很長時(shí)間, 才能重新編輯非正常解鎖的記錄。時(shí)間定得短了,則經(jīng)常退化成樂觀鎖。

  2. 時(shí)間戳精度問題。如果精度不夠,那么在加鎖時(shí),與我們討論過的樂觀鎖失效存, 在同樣的漏洞。

  3. 這種形式的鎖定,只是應(yīng)用層面的鎖定,并非數(shù)據(jù)庫層面的鎖定。 如果存在應(yīng)用之外對(duì)于數(shù)據(jù)庫的寫入操作。這個(gè)鎖定機(jī)制是無效的。

關(guān)于“Yii2.0樂觀鎖與悲觀鎖怎么用”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

分享文章:Yii2.0樂觀鎖與悲觀鎖怎么用-創(chuàng)新互聯(lián)
本文地址:http://aaarwkj.com/article32/pjspc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供響應(yīng)式網(wǎng)站定制網(wǎng)站、標(biāo)簽優(yōu)化、App開發(fā)、網(wǎng)站設(shè)計(jì)、網(wǎng)站維護(hù)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)公司
亚洲熟妇精品一区二区| 天天操夜夜操狠狠操91| 亚洲精品伦理中文字幕| 美女张开腿让男人插进去| 成人激情电影免费在线| 日本人妻三级精品久久| 99热久久精品免费精品| 国产日韩欧美视频在线观看| 久久精品国产av一一区| 密臀精品国产一区二区| 日韩性生活视频免费播放| 国产精品亚洲二区三区| 亚洲日本成人av在线观看| 日韩av网址在线免费观看| 伦理在线视频免费观看视频| 色婷婷狠狠久久综合中文一本| 国产无套内射三级视频| 最近日本免费高清完整版| 国内精品人妻中文字幕| 欧美日韩久久久久久精品| 国产一区二区三区不卡视频| 青青草视频在线好好热| 午夜18禁毛片免费看| 国产剧情av网址观看免费| 麻豆av永久地址久久精品| 免费的黄色片带中文字幕| 欧美黄片在线免费观看| 国产精品伊人久久综合网| 国内一级片内射视频播放| 亚洲乱码一区二区在线| 少妇高潮试看二十分钟| 日韩黄色精品中文视频| 精品久久激情中文字幕| 少妇38p高潮在线| 99精品亚洲一区二区| 亚洲视频精品一区二区三区| 未满十八禁止观看免费| 日韩黄片大全在线观看| 国产男女在线视频观看| 中文人妻熟妇乱又伦精品| 中文字幕日韩精品久久|