[英]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;
}
由於時間戳不匹配,這將導致中止。
筆記:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.