Lumen使用updateOrCreate()出现Duplicate entry重复键错误如何解决
问题成因
- Lumen 没有裁剪
updateOrCreate方法,该方法定义在Illuminate\Database\Eloquent\Builder类中,只要正确开启Lumen的Eloquent组件就可以正常调用,不属于被裁剪的功能。 - 触发唯一索引冲突的核心原因分为两类:
- 匹配字段预处理逻辑不一致:如果数据库
username字段的排序规则是大小写不敏感,或是模型为username定义了修改器(比如自动转小写、自动加前缀),会导致查询时使用的原始值匹配不到已存记录,写入时经修改器处理后的值和已有记录重复。 - 高并发场景下的非原子操作缺陷:
updateOrCreate底层是先执行SELECT查询匹配记录,不存在则执行INSERT,存在则执行UPDATE,两个操作没有原子性保证。多个携带相同username的请求同时进入时,都查询不到对应记录,就会同时触发INSERT操作,最终引发冲突。
解决方案
方案1:统一匹配字段预处理逻辑
如果是大小写或修改器导致的匹配异常,执行查询前先对匹配字段做和存储逻辑完全一致的预处理,比如统一转小写:
User::updateOrCreate( ['username' => strtolower($user->username)], [ 'email' => $user->email, 'password' => $user->password, 'foreign_id' => $user->foreign_id, 'client_id' => $user->client_id, 'status' => $user->active, 'user_level' => (integer) $user->user_level ] );
同时检查模型的$fillable属性是否包含所有需要写入的字段,避免赋值失败。
方案2:改用原子化upsert方法(高并发场景推荐)
upsert方法直接依赖MySQL的唯一索引实现原子化的存在则更新、不存在则写入逻辑,全程为单条SQL操作,完全避免并发冲突问题:
User::upsert( [ 'username' => $user->username, 'email' => $user->email, 'password' => $user->password, 'foreign_id' => $user->foreign_id, 'client_id' => $user->client_id, 'status' => $user->active, 'user_level' => (integer) $user->user_level ], ['username'], // 对应表的唯一索引字段 ['email', 'password', 'foreign_id', 'client_id', 'status', 'user_level'] // 匹配到记录时需要更新的字段 );
方案3:异常捕获兜底
如果是偶发的低频次并发场景,也可以通过捕获唯一索引异常做兜底重试:
try { User::updateOrCreate( ['username' => $user->username], [ 'email' => $user->email, 'password' => $user->password, 'foreign_id' => $user->foreign_id, 'client_id' => $user->client_id, 'status' => $user->active, 'user_level' => (integer) $user->user_level ] ); } catch (\Illuminate\Database\UniqueConstraintViolationException $e) { // 捕获冲突异常后直接执行更新逻辑 User::where('username', $user->username)->update([ 'email' => $user->email, 'password' => $user->password, 'foreign_id' => $user->foreign_id, 'client_id' => $user->client_id, 'status' => $user->active, 'user_level' => (integer) $user->user_level ]); }
内容的提问来源于stack exchange,提问作者SubjectX
相关产品推荐
相关产品推荐

