有两种锁定:悲观(您试图避免的一种)和乐观锁定。
在乐观锁定中,您不持有任何锁,而是尝试保存文档;如果文档已经同时被修改(意味着它在我们加载它之后就被修改了),那么你重试整个过程(加载 + 变异 + 保存)。
一种方法是使用version 列,每次更改实体时都会增加该列。当您尝试持久化时,您期望带有version = version + 1 的实体不存在。如果它已经存在,则意味着发生了并发更新,您重试(加载+变异+保存)。
在伪代码中,算法是这样的:
function updateEntity(ID, load, mutate, create)
do
{
entity, version = load(ID) or create entity
entity = mutate entity
updateRow(that matches the ID and version) and increment version
}
while (row has not changed and was not inserted)
我还会给你一个用于 MongoDB 的 PHP 代码示例(希望它易于理解):
class OptimisticMongoDocumentUpdater
{
public function addOrUpdate(Collection $collection, $id, callable $hidrator, callable $factory = null, callable $updater, callable $serializer)
{
/**
* We try to add/update the entity in a concurrent safe manner
* using optimistic locking: we always try to update the existing version;
* if another concurrent write has finished before us in the mean time
* then retry the *whole* updating process
*/
do {
$document = $collection->findOne([
'_id' => new ObjectID($id),
]);
if ($document) {
$version = $document['version'];
$entity = \call_user_func($hidrator, $document);
} else {
if (!$factory) {
return;//do not create if factory does not exist
}
$entity = $factory();
$version = 0;
}
$entity = $updater($entity);
$serialized = $serializer($entity);
unset($serialized['version']);
try {
$result = $collection->updateOne(
[
'_id' => new ObjectID($id),
'version' => $version,
],
[
'$set' => $serialized,
'$inc' => ['version' => 1],
],
[
'upsert' => true,
]
);
} catch (\MongoDB\Driver\Exception\WriteException $writeException) {
$result = $writeException->getWriteResult();
}
} while (0 == $result->getMatchedCount() && 0 == $result->getUpsertedCount());//no side effect? then concurrent update -> retry
}
}