在PHP高并发场景下,解决超卖和超扣问题需要从多个方面入手,以下是一些常见的解决方案:
1. 数据库锁机制
1.1 悲观锁
在事务中通过 SELECT ... FOR UPDATE 锁定记录,确保同一时间只有一个请求能修改数据。
$pdo->beginTransaction();
$stmt = $pdo->prepare("SELECT stock FROM products WHERE id = :id FOR UPDATE");
$stmt->execute([':id' => $productId]);
$stock = $stmt->fetchColumn();
if ($stock > 0) {
$stmt = $pdo->prepare("UPDATE products SET stock = stock - 1 WHERE id = :id");
$stmt->execute([':id' => $productId]);
$pdo->commit();
} else {
$pdo->rollBack();
throw new Exception("库存不足");
}
1.2 乐观锁
通过版本号或时间戳控制并发,更新时检查版本号是否一致。
$pdo->beginTransaction();
$stmt = $pdo->prepare("SELECT stock, version FROM products WHERE id = :id");
$stmt->execute([':id' => $productId]);
$product = $stmt->fetch(PDO::FETCH_ASSOC);
if ($product['stock'] > 0) {
$stmt = $pdo->prepare("UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = :id AND version = :version");
$stmt->execute([':id' => $productId, ':version' => $product['version']]);
if ($stmt->rowCount() > 0) {
$pdo->commit();
} else {
$pdo->rollBack();
throw new Exception("更新失败,请重试");
}
} else {
$pdo->rollBack();
throw new Exception("库存不足");
}
2. Redis 分布式锁
使用Redis的 SETNX 命令实现分布式锁,确保同一时间只有一个请求能执行关键操作。
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$lockKey = 'product_lock_' . $productId;
$lockValue = uniqid();
$lockExpire = 10; // 锁过期时间
if ($redis->set($lockKey, $lockValue, ['nx', 'ex' => $lockExpire])) {
try {
$stock = $redis->get('product_stock_' . $productId);
if ($stock > 0) {
$redis->decr('product_stock_' . $productId);
// 其他业务逻辑
} else {
throw new Exception("库存不足");
}
} finally {
if ($redis->get($lockKey) === $lockValue) {
$redis->del($lockKey);
}
}
} else {
throw new Exception("系统繁忙,请重试");
}
3. 消息队列
将请求放入消息队列,异步处理订单,避免直接操作数据库。
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$orderData = [
'product_id' => $productId,
'user_id' => $userId,
'quantity' => 1,
];
$redis->lpush('order_queue', json_encode($orderData));
消费者处理订单:
while (true) {
$orderData = $redis->rpop('order_queue');
if ($orderData) {
$order = json_decode($orderData, true);
// 处理订单逻辑
}
}
4. 限流与降级
通过限流和降级机制,防止系统过载。
4.1 限流
使用Redis或令牌桶算法限制请求速率。
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = 'rate_limit_' . $userId;
$limit = 100; // 每秒允许的请求数
if ($redis->incr($key) > $limit) {
throw new Exception("请求过于频繁,请稍后再试");
}
$redis->expire($key, 1);
4.2 降级
在高并发时,暂时关闭非核心功能,确保核心业务正常运行。
if ($isHighTraffic) {
// 关闭非核心功能
$coreService->processOrder($orderData);
} else {
// 正常处理
$normalService->processOrder($orderData);
}
5. 数据库优化
5.1 索引优化
确保库存字段有索引,提升查询效率。
5.2 分库分表
通过分库分表分散数据库压力。
6. 缓存
使用Redis等缓存库存信息,减少数据库访问。
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$stock = $redis->get('product_stock_' . $productId);
if ($stock > 0) {
$redis->decr('product_stock_' . $productId);
// 其他业务逻辑
} else {
throw new Exception("库存不足");
}
总结
解决PHP高并发下的超卖和超扣问题,通常需要结合数据库锁、分布式锁、消息队列、限流、降级、数据库优化和缓存等多种手段,具体方案应根据业务需求选择。