php处理数据库并发更新冲突的核心方案有四种:1. 行级锁(select ... for update);2. 乐观锁(版本号校验);3. 原子sql操作;4. 分布式锁。应按业务敏感度、qps和一致性要求优先选用数据库内方案。

PHP 中处理数据库并发更新冲突,核心在于避免多个请求同时修改同一行数据导致的覆盖或逻辑错误。常见场景如库存扣减、计数器累加、订单状态变更等,若不加控制,极易出现超卖、重复处理等问题。
使用数据库行级锁(SELECT ... FOR UPDATE)
在事务中对要更新的数据先行加写锁,确保其他事务必须等待锁释放后才能读取或修改该行。适用于 InnoDB 引擎,且需开启事务。
- PHP 中用 PDO 或 MySQLi 执行
START TRANSACTION,再执行带FOR UPDATE的查询 - 例如:先查库存
SELECT stock FROM products WHERE id = ? FOR UPDATE,判断足够后再执行UPDATE products SET stock = stock - 1 WHERE id = ? - 注意:锁只在事务提交(
COMMIT)或回滚(ROLLBACK)后释放;长时间持有锁会降低并发性能
基于版本号(optimistic locking)的更新校验
不在数据库加锁,而是在表中增加 version 字段,每次更新时检查版本是否匹配,不一致则拒绝更新并重试。
- 读取数据时带上当前 version:
SELECT id, stock, version FROM products WHERE id = ? - 更新时校验:
UPDATE products SET stock = ?, version = version + 1 WHERE id = ? AND version = ? - PHP 判断
affected_rows === 1,否则说明已被其他请求抢先更新,可自动重试或提示用户刷新
原子性操作替代应用层逻辑
把“读—改—写”三步合并为一条数据库原子语句,从根本上消除中间态竞争。
立即学习“PHP免费学习笔记(深入)”;
- 库存扣减直接用:
UPDATE products SET stock = stock - 1 WHERE id = ? AND stock >= 1,并检查影响行数 - 计数器自增用:
UPDATE stats SET count = count + 1 WHERE key = ? - 状态机更新可结合条件:
UPDATE orders SET status = 'paid' WHERE id = ? AND status = 'unpaid'
引入分布式锁(高并发跨服务场景)
当 PHP 实例不止一台,且业务逻辑无法完全下沉到 SQL 层时,可用 Redis 或 ZooKeeper 实现外部锁协调。
- 例如用 Redis 的
SET resource_name random_value NX PX 5000获取锁,操作完成再用 Lua 脚本校验 value 后删除 - 务必设置过期时间防止死锁,value 需唯一(如 UUID)以避免误删他人锁
- 仅在必要时使用——它增加了系统复杂度和延迟,优先考虑前三种数据库内方案
实际选型要看业务敏感度、QPS 规模和一致性要求:简单计数用原子 SQL;强一致性事务用 FOR UPDATE;多服务协作或长流程用分布式锁;对响应延迟敏感又允许少量重试的,适合乐观锁。











