Laravel 5.5
我想知道如何正确处理由不同用户或同一用户从不同页面对同一记录进行多次更新的可能情况。
例如,如果从数据库中读取Model_1的实例,响应来自Page_1的请求,并且响应来自Page_2的请求加载了同一对象的副本,那么如何最好地实现防止第二次更新的机制从破坏第一次更新? (当然,更新可以按任何顺序发生......)。
我不知道是否可以通过Eloquent锁定记录(我不想使用DB::
进行锁定,因为您必须引用基础表和行ID),但即便如此如果可能的话,加载页面时锁定和提交时解锁也不合适(我将省略细节)。
我认为检测到先前的更新已经完成并且优雅地使后续更新失败将是最好的方法,但我是否必须手动执行此操作,例如通过测试时间戳(updated_at)字段?
(我假设Eloquent在更新之前不会自动比较所有字段,因为如果使用文本/二进制等大字段,这会有点低效)
答案 0 :(得分:1)
你应该看看悲观锁定,这是一个阻止任何更新的功能,直到完成现有的更新。
查询构建器还包含一些函数,可帮助您对select语句执行“悲观锁定”。要使用“共享锁”运行语句,可以在查询中使用sharedLock方法。在您的事务提交之前,共享锁会阻止修改所选行:
DB::table('users')->where('votes', '>', 100)->sharedLock()->get();
或者,您可以使用lockForUpdate方法。 “for update”锁定可防止修改行或使用另一个共享锁选择:
DB::table('users')->where('votes', '>', 100)->lockForUpdate()->get();
答案 1 :(得分:1)
我想出的是:
<?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;
}
由于时间戳不匹配,这将导致中止。
备注:强>