Laravel 8:使用Factory播种时查询表失败引发唯一键冲突
解决方案
1. 禁用播种器事务
Laravel 播种器默认会将整个播种过程包裹在事务中,所有记录要等到全部生成完成后才会批量写入数据库,这就导致你的 slug 生成逻辑无法查询到已生成的重复值。可以在播种器中显式禁用事务:
修改 ArticlesTableSeeder 的 run 方法:
class ArticlesTableSeeder extends Seeder { public function run(): void { // 禁用事务,每条记录创建后立即写入数据库 $this->withoutTransaction(); Article::factory()->count(10000)->create(); } }
这样每次创建 Article 实例时,记录会立刻提交到数据库,你的 setSlugAttribute 修改器就能查询到之前生成的 slug,自动追加序号避免重复。
2. 分批创建记录
如果不想完全禁用事务,可以将大数量的创建任务拆分成多个小批次,每批次创建完成后提交事务,确保下一批次能查询到之前的 slug:
class ArticlesTableSeeder extends Seeder { public function run(): void { // 分100批,每批创建100条 for ($batch = 0; $batch < 100; $batch++) { Article::factory()->count(100)->create(); } } }
每一批次的创建都会独立提交,下一批次生成 slug 时就能查询到之前所有批次的记录,避免唯一键冲突。
3. 内存中跟踪已生成的slug(进阶方案)
如果追求更高的性能(减少数据库查询次数),可以在播种器中维护一个内存数组,记录已经生成的 slug,生成时先检查内存中的值,再回查数据库:
class ArticlesTableSeeder extends Seeder { public function run(): void { $usedSlugs = []; Article::factory()->count(10000)->make()->each(function ($article) use (&$usedSlugs) { $baseSlug = $article->slug; $currentSlug = $baseSlug; $counter = 1; // 先检查内存中的slug,再检查数据库 while (in_array($currentSlug, $usedSlugs) || Article::where('slug', $currentSlug)->exists()) { $currentSlug = "{$baseSlug}-{$counter}"; $counter++; } $article->slug = $currentSlug; $usedSlugs[] = $currentSlug; $article->save(); }); } }
这种方式先用 make() 创建模型实例不写入数据库,在内存中处理 slug 去重后再调用 save() 写入,既避免了事务问题,又减少了重复查询数据库的次数。
内容的提问来源于stack exchange,提问作者Matt Komarnicki
相关产品推荐
相关产品推荐

