news 2026/9/25 2:09:41

Laravel 乐观锁:高并发场景下的性能优化利器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Laravel 乐观锁:高并发场景下的性能优化利器

两个系统的故事

想象一下:高峰期到了,两家大型公司正在处理每秒数百万笔请求。

悲观系统采用了我们上篇文章讨论的传统方法——在做任何更改之前锁定每条记录。他们的数据库就像一座狭窄的桥,一次只能通过一辆车。安全吗?绝对的。快吗?不见得。

乐观系统则采用了完全不同的设计思路。他们的系统像一条多车道的高速公路,相信大多数时候车辆(请求)不会相撞。即便检测到潜在冲突,也能优雅地处理。

到下午 2 点,悲观系统的响应时间已经爬升到每笔请求 3 秒以上。而乐观系统仍然以 200ms 的平均响应时间稳定运行,处理的请求量是前者的 10 倍。

秘诀是什么?乐观锁。

什么是乐观锁?

乐观锁基于一个简单的前提:冲突很少发生,所以不必事先锁定一切。我们不在读取数据前获取锁,而是:

读取数据及其版本号

处理业务逻辑

仅在版本号未改变时才尝试更新

如果其他人同时修改了数据则重试

这就像编辑共享的 Google 文档——你不会在思考写什么的时候锁定整个文档。只有当别人在你工作时更改了同一部分,你才会收到警告。

真实场景:Maria 的午夜购物狂欢

来看看 Maria 的故事。她是个夜猫子,喜欢在午夜流量低的时候网购……至少她是这么认为的。

Maria 的乐观银行账户里有 1000 美元。在凌晨 12:00 整,她:

从科技商城订购了一台 400 美元的笔记本电脑

从活动中心购买了 300 美元的音乐会门票

从旅行公司预订了 350 美元的机票

这三笔交易在几毫秒内同时到达银行的 API。看看乐观锁如何完美解决这个问题:

Laravel 实现

让我们一步步构建,从数据库结构开始:

步骤 1:带版本列的数据库设置

// 迁移文件:为乐观锁添加版本列

Schema::table('accounts', function (Blueprint $table) {

$table->integer('version')->default(0)->after('balance');

$table->index(['account_number', 'version']);

});

步骤 2:带版本跟踪的 Account 模型

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

use App\Exceptions\OptimisticLockException;

class Account extends Model

{

protected $fillable = ['account_number', 'balance', 'version'];

protected $casts = [

'balance' => 'decimal:2'

];

/**

* 乐观锁:仅在版本匹配时更新

*/

public function updateBalanceOptimistically($newBalance, $expectedVersion = null)

{

$expectedVersion = $expectedVersion ?? $this->version;

$updated = $this->newQuery()

->where('id', $this->id)

->where('version', $expectedVersion)

->update([

'balance' => $newBalance,

'version' => $expectedVersion + 1,

'updated_at' => now()

]);

if (!$updated) {

throw new OptimisticLockException(

"账户 {$this->account_number} 已被其他交易修改"

);

}

// 更新模型实例

$this->balance = $newBalance;

$this->version = $expectedVersion + 1;

return $this;

}

}

步骤 3:自定义异常

<?php

namespace App\Exceptions;

class OptimisticLockException extends \Exception

{

public function __construct($message = "资源已被其他交易修改")

{

parent::__construct($message);

}

}

步骤 4:带重试逻辑的交易服务

<?php

namespace App\Services;

use App\Models\Account;

use App\Models\Transaction;

use App\Exceptions\OptimisticLockException;

use Illuminate\Support\Facades\DB;

use Illuminate\Support\Facades\Log;

class OptimisticTransactionService

{

const MAX_RETRIES = 3;

const RETRY_DELAY_MS = 50; // 50 毫秒

public function transfer(string $fromAccount, string $toAccount, float $amount, string $reference)

{

return $this->executeWithRetry(function() use ($fromAccount, $toAccount, $amount, $reference) {

return DB::transaction(function() use ($fromAccount, $toAccount, $amount, $reference) {

// 获取两个账户的最新副本及当前版本

$sender = Account::where('account_number', $fromAccount)->first();

$receiver = Account::where('account_number', $toAccount)->first();

if (!$sender || !$receiver) {

throw new \Exception('账户不存在');

}

// 业务逻辑验证

if ($sender->balance < $amount) {

throw new \Exception('余额不足');

}

// 计算新余额

$newSenderBalance = $sender->balance - $amount;

$newReceiverBalance = $receiver->balance + $amount;

// 乐观更新(关键就在这里)

$sender->updateBalanceOptimistically($newSenderBalance);

$receiver->updateBalanceOptimistically($newReceiverBalance);

// 记录交易

$transaction = Transaction::create([

'from_account' => $fromAccount,

'to_account' => $toAccount,

'amount' => $amount,

'reference' => $reference,

'status' => 'completed',

'sender_version_used' => $sender->version - 1,

'receiver_version_used' => $receiver->version - 1,

]);

Log::info("转账完成", [

'transaction_id' => $transaction->id,

'from' => $fromAccount,

'to' => $toAccount,

'amount' => $amount

]);

return $transaction;

});

});

}

private function executeWithRetry(callable $operation, int $attempt = 1)

{

try {

return $operation();

} catch (OptimisticLockException $e) {

if ($attempt >= self::MAX_RETRIES) {

Log::error("乐观锁重试次数超限", [

'attempts' => $attempt,

'error' => $e->getMessage()

]);

throw new \Exception("交易繁忙,请稍后重试");

}

Log::info("乐观锁冲突,正在重试", [

'attempt' => $attempt,

'next_attempt_in_ms' => self::RETRY_DELAY_MS * $attempt

]);

// 指数退避:每次重试等待更长时间

usleep(self::RETRY_DELAY_MS * $attempt * 1000);

return $this->executeWithRetry($operation, $attempt + 1);

}

}

}

步骤 5:API 控制器

<?php

namespace App\Http\Controllers;

use App\Services\OptimisticTransactionService;

use Illuminate\Http\Request;

use Illuminate\Http\JsonResponse;

class TransferController extends Controller

{

public function __construct(

private OptimisticTransactionService $transactionService

) {}

public function transfer(Request $request): JsonResponse

{

$validated = $request->validate([

'from_account' => 'required|string',

'to_account' => 'required|string',

'amount' => 'required|numeric|min:0.01',

'reference' => 'required|string|max:255'

]);

try {

$transaction = $this->transactionService->transfer(

$validated['from_account'],

$validated['to_account'],

$validated['amount'],

$validated['reference']

);

return response()->json([

'success' => true,

'transaction_id' => $transaction->id,

'message' => '转账成功'

]);

} catch (\Exception $e) {

return response()->json([

'success' => false,

'message' => $e->getMessage()

], 400);

}

}

}

回到 Maria 的故事:整个流程如何工作

当 Maria 的三笔交易同时到达系统时:

交易 A(笔记本电脑):读取 Maria 的账户(余额:$1000,版本:5)

交易 B(音乐会):同样读取账户(余额:$1000,版本:5)

交易 C(机票):同样的操作(余额:$1000,版本:5)

精彩的来了:

交易 A 首先完成:更新余额为 $600,版本为 6

交易 B 尝试更新:版本不匹配!(期望 5,实际 6)

交易 B 重试:读取最新数据(余额:$600,版本:6),成功完成

交易 C 尝试更新:又一次版本不匹配,使用最新数据重试

交易 C 失败:余额不足(余额:$300,需要:$350)

结果:Maria 成功购买了笔记本电脑和音乐会门票,但机票预订失败,并收到清晰的错误消息。没有资金损失,没有数据不一致,所有这些都在 500ms 内完成!

性能优势:数字说话

在实际的高并发系统中,从悲观锁切换到乐观锁往往能带来显著提升:

5 倍的交易吞吐量提升

60%的平均响应时间缩减

90%的数据库连接超时减少

零数据一致性问题

秘密武器?大多数交易其实并不冲突。避免不必要的锁,数据库就能腾出手来处理更多并发操作。

何时使用乐观锁

最佳应用场景:

读多写少、冲突少的场景(如文章阅读计数、商品浏览量)

电商系统的库存扣减(并发购买同一商品)

社交应用的点赞、收藏功能

用户积分、钱包余额的更新

性能至关重要的高并发系统

具有良好重试机制的应用

不适用于:

冲突频繁的场景(>10% 的操作)

重试逻辑会影响用户体验的情况

需要保证立即一致性的系统

网络延迟导致重试成本过高的环境

实战高级技巧

1. 智能版本列

// 使用时间戳而不是简单整数,便于更好的调试

Schema::table('accounts', function (Blueprint $table) {

$table->timestamp('version')->default(DB::raw('CURRENT_TIMESTAMP(6)'));

});

2. 冲突指标监控

// 添加监控以了解冲突率

class OptimisticLockMetrics

{

public static function recordConflict($model, $operation)

{

Cache::increment("optimistic_conflicts:{$model}:{$operation}:".now()->format('Y-m-d-H'));

}

}

3. 优雅降级

// 在高冲突期间回退到悲观锁

if ($this->getConflictRate() > 0.1) {

return $this->pessimisticTransfer($fromAccount, $toAccount, $amount);

}

总结

乐观锁不仅是个数据库技巧——更是一种思维方式的转变。不再一上来就锁定一切,而是采用更聪明的策略:只在冲突真正发生时才去处理。

在高并发的互联网世界中,每一毫秒的延迟都会影响用户体验,每一次失败的请求都可能流失用户。乐观锁为我们提供了两全其美的方案:极致的性能和坚如磐石的一致性。

无论你构建的是电商平台、社交应用还是支付系统,当面对高并发场景时,请记住 Maria 的故事。有时候,保持乐观不仅有益于心理健康——对应用性能同样大有裨益。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 17:04:43

1小时打造MissAV智能推荐系统原型

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 快速开发一个MissAV内容推荐系统原型&#xff0c;功能包括&#xff1a;1. 用户偏好收集 2. 内容特征提取 3. 相似度计算 4. 推荐结果展示 5. 反馈机制。使用Sentence Transformers处…

作者头像 李华
网站建设 2026/9/24 14:29:33

零基础教程:5分钟创建你的第一个Yande搜索入口

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 为初学者设计一个简单的Yande搜索引擎入口页面教程项目。包含基础HTML结构、CSS样式和极简JavaScript搜索功能。逐步指导如何添加搜索框、搜索结果展示区域和基本交互效果。提供详细…

作者头像 李华
网站建设 2026/9/24 20:41:50

AI如何自动获取国内NTP服务器IP地址

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个Python脚本&#xff0c;使用AI模型自动检测国内可用的NTP时间服务器IP地址。要求&#xff1a;1) 实现NTP协议的基本通信功能&#xff1b;2) 使用AI算法评估服务器响应时间和…

作者头像 李华
网站建设 2026/9/25 6:48:11

AI一键配置Java环境:告别繁琐手动设置

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个Java环境自动配置工具&#xff0c;能够根据用户操作系统自动检测并安装合适版本的JDK&#xff0c;自动配置JAVA_HOME和PATH环境变量&#xff0c;支持Windows/Mac/Linux多平…

作者头像 李华
网站建设 2026/9/23 16:49:09

JAVA赋能:同城无人KTV线上约唱新玩法

以下是一个基于 JAVA 开发的 同城无人KTV线上约唱系统 的创新方案&#xff0c;结合“无人化运营社交互动智能体验”&#xff0c;打造“线上预约、线下嗨唱、全程无接触”的新玩法&#xff0c;满足年轻人对娱乐场景的个性化需求。 一、系统定位&#xff1a;重构KTV消费场景&…

作者头像 李华
网站建设 2026/9/24 22:44:37

告别切换:VSCode内高效阅读技术文档与小说

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个高效率的VSCode文档阅读插件&#xff0c;特别优化技术文档和小说阅读体验&#xff1a;1.支持Markdown实时渲染 2.快速全文搜索和高亮 3.自定义快捷键翻页 4.阅读进度多设备…

作者头像 李华