php怎么实现抢购
PHP实现抢购功能
抢购功能需要解决高并发、库存准确性和防止超卖等问题。以下是实现抢购功能的关键步骤:
数据库设计
创建商品表和订单表,商品表需包含库存字段:
CREATE TABLE `product` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`stock` int(11) NOT NULL COMMENT '库存',
`version` int(11) NOT NULL DEFAULT '0' COMMENT '乐观锁版本号',
PRIMARY KEY (`id`)
);
CREATE TABLE `order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`product_id` int(11) NOT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`)
);
悲观锁实现
使用数据库行锁确保同一时间只有一个请求能操作库存:
// 开启事务
$pdo->beginTransaction();
try {
// 查询商品并加锁
$stmt = $pdo->prepare("SELECT * FROM product WHERE id = ? FOR UPDATE");
$stmt->execute([$productId]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
if ($product['stock'] > 0) {
// 减库存
$pdo->prepare("UPDATE product SET stock = stock - 1 WHERE id = ?")->execute([$productId]);
// 创建订单
$pdo->prepare("INSERT INTO order (user_id, product_id, create_time) VALUES (?, ?, NOW())")->execute([$userId, $productId]);
$pdo->commit();
echo '抢购成功';
} else {
$pdo->rollBack();
echo '库存不足';
}
} catch (Exception $e) {
$pdo->rollBack();
echo '系统异常';
}
乐观锁实现
通过版本号控制并发:
$pdo->beginTransaction();
try {
// 获取当前版本号
$stmt = $pdo->prepare("SELECT stock, version FROM product WHERE id = ?");
$stmt->execute([$productId]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
if ($product['stock'] > 0) {
// 带版本号更新
$affected = $pdo->prepare("UPDATE product SET stock = stock - 1, version = version + 1 WHERE id = ? AND version = ?")
->execute([$productId, $product['version']]);
if ($affected) {
// 创建订单
$pdo->prepare("INSERT INTO order (user_id, product_id, create_time) VALUES (?, ?, NOW())")->execute([$userId, $productId]);
$pdo->commit();
echo '抢购成功';
} else {
$pdo->rollBack();
echo '抢购失败,请重试';
}
} else {
$pdo->rollBack();
echo '库存不足';
}
} catch (Exception $e) {
$pdo->rollBack();
echo '系统异常';
}
Redis队列实现
使用Redis的原子操作处理高并发:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 商品库存预先存入Redis
$redis->set("product_{$productId}_stock", 100);
// 扣减库存
$leftStock = $redis->decr("product_{$productId}_stock");
if ($leftStock >= 0) {
// 库存足够,创建订单
$orderId = createOrder($userId, $productId);
if ($orderId) {
echo '抢购成功';
} else {
// 回滚库存
$redis->incr("product_{$productId}_stock");
echo '创建订单失败';
}
} else {
// 库存不足回滚
$redis->incr("product_{$productId}_stock");
echo '库存不足';
}
限流措施
使用Redis实现简单限流:

$key = "user_{$userId}_limit";
$limit = 10; // 每秒限制10次请求
if ($redis->get($key) >= $limit) {
die('请求过于频繁');
}
$redis->multi()
->incr($key)
->expire($key, 1)
->exec();
前端优化
- 倒计时结束后才显示购买按钮
- 按钮点击后立即禁用,防止重复提交
- 使用验证码或人机验证防止机器人
其他注意事项
- 库存预热:活动开始前将库存加载到Redis
- 异步处理:将订单创建放入消息队列异步处理
- 服务降级:核心功能优先保证,非核心功能可暂时关闭
- 数据一致性:最终通过定时任务核对Redis和数据库库存
以上方法可根据实际业务需求组合使用,悲观锁适合库存较少场景,乐观锁适合中等并发,Redis方案适合高并发场景。






