You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 09:25:22