在UPDATE语句WHERE条件中校验version字段实现乐观锁,执行后检查rowCount()是否为0来判断是否被并发修改;version字段需建索引,避免先SELECT再UPDATE的竞态,且PDO需手动检查影响行数而非依赖异常。

UPDATE 语句里怎么加版本号实现乐观锁
直接在 UPDATE 的 WHERE 条件中带上版本字段,是最简单也最常用的乐观锁写法。核心逻辑是:只允许「当前读到的版本号」与数据库里最新版本一致时才更新成功,否则影响行为为 0 行。
常见错误是只在 SET 里更新版本号,却忘了在 WHERE 中校验——这会让并发写覆盖静默发生。
UPDATE users SET name = ?, version = version + 1 WHERE id = ? AND version = ?- 执行后检查
mysqli_affected_rows()或PDOStatement::rowCount()返回值:为 0 表示被其他请求抢先更新,当前操作应失败重试 - 注意:version 字段必须有索引(至少是联合索引的一部分),否则 WHERE 条件可能全表扫描,锁粒度变大
PHP 中如何安全获取并使用 version 值
不能先 SELECT 再拼 UPDATE,中间存在竞态窗口。必须保证「读取 version」和「带 version 更新」在同一个事务上下文里原子执行,或者干脆用单条 UPDATE 完成校验+变更。
更稳妥的做法是:用一条带条件的 UPDATE 直接尝试变更,并通过返回行数判断是否成功。如果业务逻辑复杂、必须依赖查询结果做计算,那就得加事务 + SELECT ... FOR UPDATE,但这已属于悲观锁范畴,偏离了乐观锁初衷。
立即学习“PHP免费学习笔记(深入)”;
- 避免这种写法:
$row = $pdo->query("SELECT version FROM users WHERE id = 1")->fetch(); $pdo->exec("UPDATE users SET version = {$row['version']} + 1 WHERE id = 1 AND version = {$row['version']}");—— 两次网络往返之间可能已被修改 - 推荐封装成函数:
updateWithVersion($pdo, 'users', ['name' => 'new'], ['id' => 1], 'version'),内部拼出带 version 校验的 SQL 并检查影响行数 - version 字段建议用
INT UNSIGNED或BIGINT,避免溢出;不推荐用TIMESTAMP或UUID做乐观锁依据,难以精确比对
遇到 “Deadlock found when trying to get lock” 是乐观锁的问题吗
不是。MySQL 报这个错误说明发生了**行锁冲突**,通常是多个事务按不同顺序访问相同行导致的死锁,和乐观锁机制无关。乐观锁本身不持有锁(除了 UPDATE 自带的行级 X 锁,但那是瞬间的),它靠的是“校验失败就放弃”,而不是“等锁释放”。
如果你在用乐观锁时频繁见到死锁,大概率是:
- SQL 中 WHERE 条件没走索引,导致锁升级为间隙锁或表锁
- UPDATE 涉及多行且顺序不一致(比如 A 事务先改 id=1 再改 id=2,B 事务反过来)
- 事务里混用了乐观锁和
SELECT ... FOR UPDATE等显式锁语句
检查 SHOW ENGINE INNODB STATUS 的 LATEST DETECTED DEADLOCK 部分,确认锁等待链路。乐观锁场景下,更常见的失败信号是 rowCount() === 0,而不是死锁异常。
为什么 PDO 默认不抛异常,导致乐观锁失败被忽略
因为 PDO 默认错误模式是 PDO::ERRMODE_SILENT,UPDATE 即使影响 0 行也不会报错——它只是“成功执行了一条没改到数据的语句”。很多开发者没检查返回值,误以为更新成功了。
- 务必开启异常模式:
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - 但注意:异常模式不会让
rowCount() === 0抛异常,它只对 SQL 语法错误、连接断开等底层问题起作用 - 所以最终仍要手动判断:
if ($stmt->rowCount() === 0) { throw new OptimisticLockException('Version mismatch'); } - 别依赖
try/catch捕获乐观锁失败,那会掩盖真正的数据库错误
version 字段本身不解决所有并发问题,它只保“单行更新”的原子性。涉及多表、多行或业务状态机流转时,需要配合应用层重试、幂等设计或更高阶的分布式锁策略。



















