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

Laravel firstOrNew产生重复数据问题排查与解决咨询

碰到过一模一样的问题!在800-1000+在线用户的高并发场景下,用firstOrNew向JawabanUserM插入数据出现重复,核心问题出在非原子操作和缺乏数据库层面的唯一约束这两点上,结合你的环境很容易触发竞态条件。

问题成因
  • firstOrNew的非原子特性:这个方法的执行逻辑是先跑SELECT查询记录是否存在,不存在就创建新模型实例,最后调用save()执行INSERT。这两步完全独立,高并发时多个请求会同时通过SELECT判断“记录不存在”,随后各自执行INSERT,最终产生重复数据。
  • 缺少数据库唯一约束:如果JawabanUserM表没有针对业务唯一的字段(比如user_id+question_id这类组合)设置唯一索引/约束,MySQL不会主动拦截重复插入请求——哪怕业务逻辑上这些记录本该唯一。
  • PHP-FPM的无状态多进程:PHP-FPM是多进程模式,每个请求都是独立进程,没有共享内存或状态,多个进程处理相同用户的答案请求时,完全不知道其他进程的操作,都会走到插入步骤。
解决办法

1. 先加数据库唯一约束(最关键的防线)

无论用什么代码优化,数据库层面的约束都是最后且最可靠的保障。假设业务中一个用户对同一个问题只能有一个答案,给user_id和question_id添加联合唯一索引:

ALTER TABLE JawabanUserM ADD UNIQUE INDEX idx_user_question (user_id, question_id);

这一步必须做,否则任何代码层面的处理都无法100%避免重复。

2. 替换firstOrNew为原子操作方法

方案A:用firstOrCreate并捕获重复异常

firstOrCreate自动完成“查询-不存在则插入”逻辑,当触发唯一约束时会抛出QueryException,我们可以捕获异常并返回已存在的记录:

try {
    $jawaban = JawabanUserM::firstOrCreate(
        ['user_id' => $userId, 'question_id' => $questionId], // 唯一查询条件
        ['answer' => $answer, 'created_at' => now()] // 插入的额外字段
    );
} catch (\Illuminate\Database\QueryException $e) {
    // 捕获MySQL唯一约束冲突错误(错误码1062)
    if ($e->getCode() == 1062) {
        $jawaban = JawabanUserM::where('user_id', $userId)->where('question_id', $questionId)->first();
    } else {
        // 其他异常重新抛出
        throw $e;
    }
}

方案B:用updateOrInsert(适合需要更新的场景)

如果用户重复提交答案时需要更新已有记录,updateOrInsert是原子操作,会自动判断插入或更新:

JawabanUserM::updateOrInsert(
    ['user_id' => $userId, 'question_id' => $questionId], // 唯一匹配条件
    ['answer' => $answer, 'updated_at' => now()] // 要插入或更新的字段
);

方案C:用insertOrIgnore(仅确保存在,不更新)

如果只需要确保记录存在、不需要处理更新,insertOrIgnore会忽略唯一约束冲突的错误:

DB::table('JawabanUserM')->insertOrIgnore([
    'user_id' => $userId,
    'question_id' => $questionId,
    'answer' => $answer,
    'created_at' => now(),
    'updated_at' => now()
]);
// 如需获取已存在的记录,后续再执行一次查询
$jawaban = JawabanUserM::where('user_id', $userId)->where('question_id', $questionId)->first();

3. 事务+悲观锁的补充优化(可选)

如果对数据一致性要求极高,可以在事务中使用悲观锁,但注意这会增加锁等待时间,可能影响性能:

DB::transaction(function () use ($userId, $questionId, $answer) {
    // 用lockForUpdate()锁定查询结果,防止其他事务插入
    $jawaban = JawabanUserM::where('user_id', $userId)->where('question_id', $questionId)->lockForUpdate()->first();
    if (!$jawaban) {
        $jawaban = JawabanUserM::create([
            'user_id' => $userId,
            'question_id' => $questionId,
            'answer' => $answer
        ]);
    }
    return $jawaban;
});

内容的提问来源于stack exchange,提问作者user1927503

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 09:01:29