如何优化Laravel Eloquent的firstOrCreate()以移除前置select查询
批量插入低概率重复场景的优化方案
原生Eloquent方法
Laravel 8.23.0及以上版本已经内置了你需要的逻辑的原生实现:createOrFirst()方法,它的执行逻辑和你手写的try-catch完全一致:优先尝试直接插入数据,触发唯一键冲突异常时再执行查询返回已存在的记录,绝大多数场景下只需要1次查询,比默认firstOrCreate()少一次select,完美适配你说的重复率只有1%-2%的场景。
用法示例:
// 第一个参数是唯一键匹配条件,第二个参数是额外插入字段 $output = MyModel::createOrFirst( ['field1' => 'value1'], ['field2' => 'value2', 'field3' => 'value3'] ); $outputId = $output->id;
该方法已经做了跨数据库的错误码适配、并发冲突重试等边界处理,比自行封装的try-catch逻辑更稳妥。
最佳实践建议
- 前提要求:用作重复判断的字段必须设置数据库唯一索引,否则插入不会触发重复冲突错误,逻辑完全不生效。
- 超大量数据批量处理场景:如果单条插入的吞吐量满足不了需求,可以先用
insertOrIgnore方法一次性批量插入所有数据(自动忽略重复条目,不会抛出异常),再批量查询所有对应记录的ID,数据库交互次数可以从N次降到2次,性能提升非常明显:
// 待批量插入的数据集 $dataList = [ ['field1' => 'value1', 'field2' => 'xxx'], ['field1' => 'value2', 'field2' => 'yyy'], // 其他数千/数万条数据 ]; // 批量插入,忽略重复项 MyModel::insertOrIgnore($dataList); // 一次性查询所有对应记录的ID $existIds = MyModel::whereIn('field1', array_column($dataList, 'field1')) ->pluck('id', 'field1');
- 低版本Laravel兼容方案:如果你的Laravel版本低于8.23无法使用
createOrFirst(),你手写的try-catch逻辑可以正常使用,但是需要补充异常兜底逻辑:非1062错误码的异常需要继续抛出,避免吞掉其他SQL错误。
内容的提问来源于stack exchange,提问作者vlauciani
相关产品推荐
相关产品推荐

