本文目录导读:

在 PHP 中实现“无锁变更”(通常指数据库层面的无锁更新),主要有以下几种方案:
乐观锁(CAS - Compare and Swap)
最常用且推荐的方式:
// 方案1:使用版本号
$userId = 1;
$newBalance = 500;
$version = 3; // 从查询中获取
$sql = "UPDATE user_accounts
SET balance = ?, version = version + 1
WHERE user_id = ? AND version = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$newBalance, $userId, $version]);
// 检查是否更新成功
if ($stmt->rowCount() === 0) {
// 版本不匹配,说明其他请求已修改
// 重试或返回冲突
}
// 方案2:使用时间戳
$sql = "UPDATE user_accounts
SET balance = ?, updated_at = NOW()
WHERE user_id = ? AND updated_at = ?";
数据库原子操作(适合数值型)
// 使用数据库自带的原子操作
// 不需要先查询,直接更新
$sql = "UPDATE user_accounts
SET balance = balance + ?
WHERE user_id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([100, $userId]);
// 原子更新,不需要锁
Redis 分布式锁(跨进程)
// 使用 Redis 实现分布式锁
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$lockKey = "user:{$userId}:lock";
$token = uniqid();
// 获取锁(使用 SET NX 原子操作)
if ($redis->set($lockKey, $token, ['NX', 'EX' => 10])) {
try {
// 执行更新操作
updateDatabase($userId);
} finally {
// 释放锁(使用 Lua 脚本保证原子性)
$script = "
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
";
$redis->eval($script, [$lockKey, $token], 1);
}
}
条件更新(SQL WHERE 条件)
// 利用 SQL 条件表达式实现无锁
$sql = "UPDATE orders
SET status = 'paid', updated_at = NOW()
WHERE id = ? AND status = 'pending'";
$stmt = $pdo->prepare($sql);
$stmt->execute([$orderId]);
if ($stmt->rowCount() > 0) {
// 更新成功,说明状态符合预期
}
完整示例:乐观锁重试机制
class OptimisticLockUpdater {
private $pdo;
private $maxRetries = 3;
public function updateWithRetry($userId, callable $operation) {
for ($i = 0; $i < $this->maxRetries; $i++) {
// 1. 读取当前版本
$data = $this->pdo->query(
"SELECT balance, version FROM user_accounts WHERE user_id = {$userId}"
)->fetch(PDO::FETCH_ASSOC);
$currentVersion = $data['version'];
// 2. 执行业务逻辑
$newBalance = $operation($data['balance']);
// 3. 尝试更新(条件是版本号匹配)
$stmt = $this->pdo->prepare(
"UPDATE user_accounts
SET balance = ?, version = version + 1
WHERE user_id = ? AND version = ?"
);
$stmt->execute([$newBalance, $userId, $currentVersion]);
//4. 检查是否成功
if ($stmt->rowCount() > 0) {
return $newBalance; // 成功
}
// 5. 失败则稍后重试
usleep(100000); // 100ms
}
throw new Exception("更新失败,达到最大重试次数");
}
}
数据库特定优化
// MySQL 使用 FOR UPDATE 忽略(其实这是悲观锁)
// 但可以在事务中配合条件使用
// PostgreSQL 的 SKIP LOCKED(避免等待)
$sql = "SELECT * FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED";
注意事项
-
选择场景:
- 修改频率低 → 乐观锁
- 数据库单节点 → 原子操作
- 多实例部署 → Redis 分布式锁
-
性能考虑:
- 乐观锁在高竞争下会导致大量重试
- 原子操作用途有限(只支持数值运算)
-
特殊情况:
- 更新前需要检查状态 → 条件更新
- 需要保证数据一致性 → 事务 + 锁
-
避免陷阱:
- 版本号最好用整数而非时间戳
- 重试时注意指数退避
- 数据库更新和版本检查必须在同一条 SQL 中
推荐优先使用乐观锁,因为它性能好且不需要额外组件,大多数业务场景都适用。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播