PHP并发重复请求处理方案咨询:如何避免重复创建记录?
解决并发请求下重复创建记录的问题
你的问题核心是竞态条件:两个请求几乎同时执行“检查是否存在→创建”的逻辑,第一个请求还没完成创建,第二个请求的检查就已经执行了,导致两次都认为不存在,最终生成重复记录。随机延迟无效是因为无法从根本上避免竞态,反而会降低性能、增加不确定性。
以下是几种可靠的解决方案,按优先级排序:
1. 数据库层面添加唯一约束(最推荐)
直接在数据库的唯一标识字段(比如请求中的业务唯一ID)上添加唯一索引,从底层拦截重复数据。同时使用数据库的原子化插入语句,确保“检查+创建”是原子操作。
示例(MySQL)
首先给表添加唯一索引:
ALTER TABLE your_table ADD UNIQUE INDEX idx_unique_business_id (business_id);
然后使用INSERT ... ON DUPLICATE KEY UPDATE执行插入:
// 假设$pdo是PDO实例,$businessId是请求中的唯一业务标识 $sql = "INSERT INTO your_table (business_id, field1, field2) VALUES (:business_id, :field1, :field2) ON DUPLICATE KEY UPDATE id=id"; // 存在则不做任何操作 $stmt = $pdo->prepare($sql); $stmt->execute([ 'business_id' => $businessId, 'field1' => $field1Value, 'field2' => $field2Value ]); // 可选:判断操作结果 $affectedRows = $stmt->rowCount(); if ($affectedRows === 1) { // 成功创建新记录 } else { // 记录已存在,无需重复创建 }
如果是PostgreSQL,可使用INSERT ... ON CONFLICT DO NOTHING:
INSERT INTO your_table (business_id, field1, field2) VALUES ($1, $2, $3) ON CONFLICT (business_id) DO NOTHING;
2. 分布式锁(多实例部署场景)
如果你的脚本是多实例部署(比如多个服务器或容器),需要用分布式锁来保证同一时间只有一个请求能执行创建逻辑。常用的实现方式是Redis或ZooKeeper。
Redis锁示例(PHP)
$redis = new Redis(); $redis->connect('127.0.0.1', 6379); $lockKey = "lock:entry:" . $businessId; // 用业务唯一ID生成锁键 $lockValue = uniqid(); // 生成唯一值,防止误释放其他请求的锁 $lockExpire = 10; // 锁过期时间(秒),避免死锁 // 尝试获取锁(NX=不存在才设置,EX=自动过期) if ($redis->set($lockKey, $lockValue, ['NX', 'EX' => $lockExpire])) { try { // 此处执行原有的检查+创建逻辑 $exists = checkEntryExists($businessId); if (!$exists) { createEntry($businessId, $field1Value, $field2Value); } } finally { // 释放锁时验证值,确保不会删除其他请求持有的锁 if ($redis->get($lockKey) === $lockValue) { $redis->del($lockKey); } } } else { // 获取锁失败,说明已有请求在处理 // 可选择重试几次,或直接返回“记录已存在” $retryCount = 0; while ($retryCount < 3) { usleep(100000); // 等待100ms后重试 if ($redis->set($lockKey, $lockValue, ['NX', 'EX' => $lockExpire])) { try { $exists = checkEntryExists($businessId); if (!$exists) { createEntry($businessId, $field1Value, $field2Value); } } finally { if ($redis->get($lockKey) === $lockValue) { $redis->del($lockKey); } } break; } $retryCount++; } }
3. 本地锁(单实例部署场景)
如果脚本是单实例运行,可使用本地文件锁或内存锁来避免并发执行。
PHP文件锁示例
$lockFile = __DIR__ . "/locks/{$businessId}.lock"; $lockHandle = fopen($lockFile, 'w'); // 获取排他锁(LOCK_EX),阻塞直到获取到锁 if (flock($lockHandle, LOCK_EX)) { try { // 执行检查+创建逻辑 $exists = checkEntryExists($businessId); if (!$exists) { createEntry($businessId, $field1Value, $field2Value); } } finally { flock($lockHandle, LOCK_UN); // 释放锁 fclose($lockHandle); unlink($lockFile); // 可选:删除锁文件 } } else { // 获取锁失败,直接返回或重试 }
内容的提问来源于stack exchange,提问作者skywind
相关产品推荐
相关产品推荐

