本文目录导读:

在 Laravel 中使用乐观锁(Optimistic Locking)主要通过版本号机制实现,以下是几种常见的使用方法:
使用 Eloquent 内置特性
模型准备
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $fillable = ['name', 'price', 'stock'];
}
迁移文件添加版本字段
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->decimal('price', 10, 2);
$table->integer('stock');
$table->integer('version')->default(1); // 乐观锁版本字段
$table->timestamps();
});
}
手动实现乐观锁
基础实现方法
use App\Models\Product;
use Illuminate\Database\Eloquent\ModelNotFoundException;
public function updateStock($productId, $quantity)
{
// 1. 查询当前版本号
$product = Product::findOrFail($productId);
$currentVersion = $product->version;
try {
// 2. 带版本条件更新
$updated = Product::where('id', $productId)
->where('version', $currentVersion)
->update([
'stock' => $product->stock - $quantity,
'version' => $currentVersion + 1
]);
// 3. 检查是否更新成功
if ($updated === 0) {
throw new \Exception('数据已被其他用户修改,请刷新重试');
}
return true;
} catch (\Exception $e) {
// 处理并发冲突
Log::error('乐观锁更新失败: ' . $e->getMessage());
return false;
}
}
使用 trait 封装乐观锁
创建 Trait
<?php
namespace App\Traits;
trait OptimisticLocking
{
protected string $versionColumn = 'version';
public function getVersionColumn(): string
{
return $this->versionColumn ?? 'version';
}
public function incrementVersion(): void
{
$this->increment($this->getVersionColumn());
}
public function updateWithLock(array $data): bool
{
$currentVersion = $this->{$this->getVersionColumn()};
$updated = static::where($this->getKeyName(), $this->getKey())
->where($this->getVersionColumn(), $currentVersion)
->update(array_merge($data, [
$this->getVersionColumn() => $currentVersion + 1
]));
if ($updated === 0) {
throw new \Exception('乐观锁更新失败,数据已被修改');
}
return true;
}
}
在模型中使用
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\OptimisticLocking;
class Product extends Model
{
use OptimisticLocking;
protected $fillable = ['name', 'price', 'stock'];
protected string $versionColumn = 'version';
}
使用数据库事务优化
结合事务的乐观锁
use Illuminate\Support\Facades\DB;
use App\Models\Order;
public function createOrder($productId, $userId, $quantity)
{
DB::beginTransaction();
try {
$product = Product::findOrFail($productId);
$currentVersion = $product->version;
// 检查库存
if ($product->stock < $quantity) {
throw new \Exception('库存不足');
}
// 带版本号的更新
$updated = Product::where('id', $productId)
->where('version', $currentVersion)
->update([
'stock' => $product->stock - $quantity,
'version' => $currentVersion + 1
]);
if ($updated === 0) {
throw new \Exception('并发冲突,请重试');
}
// 创建订单记录
Order::create([
'user_id' => $userId,
'product_id' => $productId,
'quantity' => $quantity
]);
DB::commit();
return true;
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
}
使用 Laravel 辅助函数
使用 updateOrCreate 方法
$product = Product::updateOrCreate(
['id' => $productId, 'version' => $currentVersion],
[
'stock' => $newStock,
'version' => $currentVersion + 1
]
);
完整示例:API 控制器
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class ProductController extends Controller
{
public function purchase(Request $request, $productId)
{
$request->validate([
'quantity' => 'required|integer|min:1',
'version' => 'required|integer'
]);
try {
$product = Product::findOrFail($productId);
$quantity = $request->input('quantity');
$clientVersion = $request->input('version');
// 检查客户端版本是否匹配
if ($clientVersion !== $product->version) {
return response()->json([
'success' => false,
'message' => '数据已过期,请刷新页面',
'current_version' => $product->version
], 409); // 409 Conflict
}
// 检查库存
if ($product->stock < $quantity) {
return response()->json([
'success' => false,
'message' => '库存不足,当前库存:' . $product->stock
], 400);
}
// 执行更新
DB::beginTransaction();
$updated = Product::where('id', $productId)
->where('version', $clientVersion)
->update([
'stock' => $product->stock - $quantity,
'version' => $clientVersion + 1
]);
if ($updated === 0) {
throw new \Exception('乐观锁冲突');
}
DB::commit();
// 返回更新后的数据
$product->refresh();
return response()->json([
'success' => true,
'data' => [
'stock' => $product->stock,
'version' => $product->version
],
'message' => '购买成功'
]);
} catch (\Exception $e) {
DB::rollBack();
Log::error('购买失败', [
'product_id' => $productId,
'error' => $e->getMessage()
]);
return response()->json([
'success' => false,
'message' => '操作失败,请重试'
], 500);
}
}
}
前端配合示例
// 前端请求示例
async function purchaseProduct(productId, quantity) {
// 先获取当前版本号
const product = await fetch(`/api/products/${productId}`).then(r => r.json());
try {
const response = await fetch(`/api/products/${productId}/purchase`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({
quantity: quantity,
version: product.version // 传递当前版本号
})
});
const result = await response.json();
if (!result.success) {
if (response.status === 409) {
// 版本冲突,提示用户刷新
alert('数据已过期,请刷新页面');
window.location.reload();
} else {
alert(result.message);
}
return;
}
// 更新成功
console.log('购买成功', result.data);
} catch (error) {
console.error('请求失败:', error);
}
}
注意事项
- 版本字段建议设置为整数类型,便于比较和自增
- 每次更新必须同时更新版本号,否则乐观锁会失效
- 事务中要捕获乐观锁异常,确保回滚
- 前端也要配合传递当前版本号,形成完整闭环
- 高并发场景下,建议结合数据库索引和连接池优化
乐观锁适用于读多写少的场景,可以有效解决并发更新问题,但会增加额外的版本检查开销。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播