簡體   English   中英

如何在Laravel Eloquent中處理多個並發更新?

[英]How to handle multiple concurrent updates in Laravel Eloquent?

Laravel 5.5

我想知道如何正確地處理由單獨的用戶或同一用戶從不同頁面對同一記錄進行多次更新的可能情況。

例如,如果從數據庫讀取Model_1的實例以響應來自Page_1的請求,並且加載了同一對象的副本以響應來自Page_2的請求,那么如何最好地實現一種機制來防止第二次更新破壞了第一次更新? (當然,更新可以按任何順序進行...)。

我不知道是否可以通過Eloquent鎖定記錄(我不想使用DB::進行鎖定,因為您必須引用基礎表和行ID),但是即使有可能,加載頁面時鎖定和提交時解鎖也不合適(我將省略詳細信息)。

我認為檢測先前已進行的更新並適當地使后續更新失敗會是最好的方法,但是我是否必須手動執行此操作,例如通過測試timestamp(updated_at)字段?

(我假設Eloquent不會在更新之前自動比較所有字段,因為如果使用大字段(例如文本/二進制),這會顯得效率不高)

您應該看一下悲觀鎖定,它是一項功能,可防止在現有操作完成之前進行任何更新。

查詢構建器還包括一些功能,可幫助您對select語句執行“悲觀鎖定”。 要使用“共享鎖”運行語句,可以在查詢上使用sharedLock方法。 共享鎖可防止在事務提交之前修改選定的行:

DB::table('users')->where('votes', '>', 100)->sharedLock()->get();

或者,您可以使用lockForUpdate方法。 “用於更新”鎖可防止修改行或使用另一個共享鎖選擇行:

DB::table('users')->where('votes', '>', 100)->lockForUpdate()->get();

參考: Laravel文檔

我想到的是:

<?php

namespace App\Traits;

use Illuminate\Support\Facades\DB;

trait UpdatableModelsTrait
{
    /**
     * Lock record for update, validate updated_at timestamp,
     * and return true if valid and updatable, throws otherwise.
     * Throws on error.
     *
     * @return bool
     */
    public function update_begin()
    {
        $result = false;
        $updated_at = DB::table($this->getTable())
            ->where($this->primaryKey, $this->getKey())
            ->sharedLock()
            ->value('updated_at');
        $updated_at = \Illuminate\Support\Carbon::createFromFormat('Y-m-d H:i:s', $updated_at);
        if($this->updated_at->eq($updated_at))
            $result = true;
        else
            abort(456, 'Concurrency Error: The original record has been altered');
        return $result;
    }

    /**
     * Save object, and return true if successful, false otherwise.
     * Throws on error.
     *
     * @return bool
     */
    public function update_end()
    {
        return parent::save();
    }

    /**
     * Save object after validating updated_at timestamp,
     * and return true if successful, false otherwise.
     * Throws on error.
     *
     * @return bool
     */
    public function save(array $options = [])
    {
        return $this->update_begin() && parent::save($options);
    }
}

用法示例:

try {
    DB::beginTransaction()
    $test1 = Test::where('label', 'Test 1')->first();
    $test2 = Test::where('label', 'Test 1')->first();
    $test1->label = 'Test 1a';
    $test1->save();
    $test2->label = 'Test 1b';
    $test2->save();
    DB::commit();
} catch(\Exception $x) {
    DB::rollback();
    throw $x;
}

由於時間戳不匹配,這將導致中止。

筆記:

  • 僅當存儲引擎支持行鎖時,此方法才能正常工作。 InnoDB可以。
  • 有一個起點和終點,因為您可能需要更新多個(可能相關的)模型,並希望在嘗試保存之前先查看是否可以在所有模型上獲得鎖。 一種替代方法是簡單地嘗試保存並在發生故障時回滾。
  • 如果願意,可以對事務使用閉包
  • 我知道自定義的HTTP響應(456)可能被認為是不正確的做法,但是您可以將其更改為返回false或throw或500 ...
  • 如果您不喜歡特征,則將實現放入基本模型中
  • 必須更改原始代碼以使其自成體系:如果發現任何錯誤,請發表評論。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM