PHP利用flock解决高并发下单库存超卖
在高并发的电商抢购或秒杀活动中,多线程或多进程同时读取并更新数据库会导致“超卖”现象(库存变为负数)。本文介绍一种基于PHP `flock`函数的轻量级解决方案,通过文件排他锁串行化处理下单请求,在保证数据一致性的同时兼顾系统稳定性。
问题分析:为什么会出现库存超卖?
假设购买商品的完整流程涉及两条主要SQL语句:
sql1: 查询当前商品库存。sql2: 执行库存扣减操作(如UPDATE table SET stock = stock - 1)。
在没有并发控制的情况下,若同时有两个用户发起请求且剩余库存仅剩1件:
- 两个请求同时执行
sql1,查到的库存均大于0。 - 随后两个请求都成功执行
sql2。 - 最终结果:库存从1变为-1,产生超卖事故。
解决方案:文件排他锁机制
解决此类并发问题的常见思路包括乐观锁、队列串行化以及原子性SQL判断等。其中,flock 文件锁是一种在单服务器或多个服务器共享存储环境下,控制进程/线程访问代码块的有效手段。
其核心逻辑是:在处理订单前,先尝试获取锁;如果锁定成功,则执行下单逻辑;否则根据策略拒绝请求或等待。
第1步:准备锁文件路径
在脚本目录下创建一个用于同步的信号文件,例如 lock.txt。该文件本身只作为互斥信号,无需维护内容。
第2步:编写阻塞模式下的锁控制逻辑
适用场景:希望请求排队处理,不直接拒绝用户,但用户可能需要较长时间等待结果。
在此模式下,如果锁已被其他进程占用,当前进程将一直等待直到获取到锁。这可能导致大量请求堆积,消耗内存和连接资源,需谨慎使用。
<?php
// 打开锁文件
$fp = fopen("lock.txt", "w+");
// 尝试获取独占锁(阻塞模式),直到获取成功
if (flock($fp, LOCK_EX)) {
// ===========================
// 关键代码区域:处理下单业务逻辑
// 此处应包含生成订单号、写入数据库库存扣减等操作
// ===========================
// 业务处理完毕后,释放锁
flock($fp, LOCK_UN);
}
// 关闭文件句柄
fclose($fp);
?>
第3步:编写非阻塞模式下的锁控制逻辑
适用场景:追求响应速度,如果无法立即获得锁,直接告知用户“系统繁忙”,保护服务器不被瞬时海量请求压垮。
通过在 flock 中增加 LOCK_NB 标志位实现非阻塞。如果返回 false,说明锁被占用,程序跳过业务逻辑直接报错退出。
<?php
$fp = fopen("lock.txt", "w+");
// 尝试获取独占锁,添加 LOCK_NB 实现非阻塞
if (flock($fp, LOCK_EX | LOCK_NB)) {
// 获取锁成功,执行下单逻辑
// ... 业务代码 ...
flock($fp, LOCK_UN);
} else {
// 获取锁失败,提示用户
echo "系统繁忙,请稍后再试";
// 必须关闭句柄,避免资源泄露
}
fclose($fp);
?>
实施要点与风险提示
1. 必须正确释放和关闭资源
在 try-catch 结构或正常的流程结束处,务必调用 flock($fp, LOCK_UN) 释放锁,并在最后调用 fclose($fp)。未正确释放锁会导致后续请求永远无法获取资源,造成服务挂起。
2. 性能考量
flock 本质上是将并发转化为串行。对于极高并发的秒杀场景,单文件的锁竞争会成为瓶颈,建议结合 Redis Lua 脚本或数据库乐观锁实现更复杂的分布式限流。但在中小规模活动或单机部署中,这是一种简单有效的方案。
3. 文件权限检查
确保运行PHP进程的Web服务器用户(如 www-data, apache, nginx)具有对 lock.txt 文件的读写权限,否则 fopen 或 flock 会静默失败或引发警告。
总结
利用PHP的 flock 函数进行文件排他锁,是解决本地进程并发下库存超卖的一种直观且易实现的方案。开发时需区分“阻塞”与“非阻塞”两种模式以匹配业务需求,并严格管理锁的释放,以避免死锁。
以上就是使用PHP flock解决高并发入库导致库存负数的详细内容,更多关于PHP并发优化的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









