php并发扣库存需据场景选锁:索引命中用行锁,否则表锁;for update用于强一致“查改”,lock in share mode用于只读校验;死锁靠固定顺序、短事务、重试规避;乐观锁用version字段实现无锁更新。

数据库锁类型与 PHP 应用场景对应关系
面试常问“PHP 中怎么处理并发扣库存”,核心其实是考察对数据库锁的理解和落地能力。InnoDB 默认行级锁,但是否真能锁住某一行,取决于 SQL 是否命中索引。例如 UPDATE products SET stock = stock - 1 WHERE id = 123,如果 id 是主键,会加行锁;若写成 WHERE name = 'iPhone' 且 name 没索引,就会升级为表锁,极大影响并发性能。
MySQL 显式加锁:SELECT ... FOR UPDATE vs LOCK IN SHARE MODE
PHP 中常用 PDO 或 MySQLi 执行带锁查询。关键区别在于:
- SELECT * FROM orders WHERE order_no = 'ORD001' FOR UPDATE:加排他锁(X 锁),其他事务无法读写该行,适合“查+改”强一致性场景,如秒杀下单前校验库存
- SELECT * FROM config WHERE key = 'site_status' LOCK IN SHARE MODE:加共享锁(S 锁),允许其他事务加 S 锁读,但阻塞 X 锁,适合只读校验类操作
- 注意:FOR UPDATE 必须在事务中执行(beginTransaction()),且事务结束前锁不释放;没提交或回滚,会导致锁等待甚至超时
死锁如何产生?PHP 层怎么规避?
死锁不是 PHP 的 bug,而是多个事务按不同顺序访问相同资源导致。比如事务 A 先锁 id=1 再锁 id=5,事务 B 反过来先锁 id=5 再锁 id=1,就可能死锁。MySQL 会自动检测并回滚其中一个事务(报错 Deadlock found when trying to get lock)。
PHP 层应对策略:
立即学习“PHP免费学习笔记(深入)”;
- 固定 DML 操作顺序:所有业务统一按主键升序更新多行,避免交叉加锁
- 缩短事务生命周期:查完立刻处理,不要在事务里做 HTTP 请求、文件读写等耗时操作
- 捕获死锁异常并重试(最多 2–3 次),用 try-catch 包裹关键事务逻辑
乐观锁在 PHP 中的轻量实现
不适合高冲突场景,但比悲观锁更省资源。典型做法是在表中加 version 字段:
UPDATE goods SET stock = stock - 1, version = version + 1 WHERE id = 123 AND version = 5
PHP 中判断 $pdo->exec() 返回值是否为 1:等于 1 表示更新成功;等于 0 表示 version 不匹配,发生并发修改,可提示用户“库存已变化,请刷新重试”或自动重读重试。
注意:乐观锁不依赖数据库锁机制,无锁等待开销,但业务层必须处理更新失败分支。











