Laravel7使用create创建用户极慢 触发万级重复SQL查询
Laravel Eloquent create方法创建用户性能异常排查
问题现象
- 调用Eloquent的
create方法创建新用户耗时长达60-120秒 - 直接通过DB门面的
insertGetId方法写入用户数据可瞬间完成 - 开启数据库查询日志跟踪
create方法执行流程,发现insert写入操作执行前,累计产生10680条结构完全一致的重复exists查询,单条查询耗时约41.34ms,累计耗时占总耗时99%以上。单条查询内容如下:
[ "query" => "select exists(select * from `users` where `varSlug` = ? and `id` != ?) as `exists`", "bindings" => [ 0 => "", // varSlug绑定值为空 1 => 0 // 排除的id值为0(新模型默认id值) ], "time" => 41.34 ]
根因定位
这个问题是模型层逻辑出现递归循环触发导致的,和Eloquent本身的性能无关:
- 重复的exists查询是
varSlug字段的唯一性校验逻辑,正常场景下保存用户前该校验只会执行1次 - 你传入的待写入属性数组里没有
varSlug字段,所以每次校验时取到的varSlug都是空值,排除id都是新模型默认的0,才会出现一万多条参数完全相同的重复查询 - DB门面的
insertGetId直接走查询构造器执行写入,完全跳过Eloquent的模型事件、属性修改器、全局作用域、校验逻辑,所以不会触发这个循环bug,执行速度正常 - 递归触发的常见位置:
- User模型
boot/booted方法中注册的saving/creating事件闭包 - User模型绑定的观察器(Observer)中的对应保存方法
- 模型上挂载的
varSlug字段修改器(setVarSlugAttribute) - User模型注册的全局作用域(Global Scope)
最典型的错误写法就是在saving/creating事件回调中手动调用$model->save()方法,会直接再次触发saving事件,形成无限递归,直到PHP进程达到递归上限才终止,过程中会反复执行slug唯一性校验,累计出上万条重复查询。
- User模型
排查步骤
- 打开User模型文件,逐一检查上述提到的事件、观察器、修改器、全局作用域代码,重点定位所有和
varSlug字段生成、唯一性校验相关的逻辑 - 找到重复调用save、重复触发查询的递归点:可以临时在唯一性校验的代码位置打印
debug_backtrace()看调用栈,能直接定位是哪段代码在反复触发查询 - 检查是否存在重复挂载校验逻辑的问题:比如同时在模型事件、表单验证、观察器里写了相同的varSlug唯一性校验,虽然不会导致一万次查询,但也会产生不必要的重复开销
修复方案
- 核心修复:解决递归调用问题。以下是最常见的错误写法和对应修正:
错误写法(会触发无限递归)
protected static function booted() { static::saving(function (User $user) { if (empty($user->varSlug)) { $user->varSlug = strtolower(trim($user->name)); } // 错误:saving事件中手动调用save,会再次触发saving事件形成递归 $user->save(); }); }正确写法
protected static function booted() { static::saving(function (User $user) { if (empty($user->varSlug)) { $user->varSlug = strtolower(trim($user->name)); } // 唯一性校验仅执行1次,saving事件执行完框架会自动完成后续保存,无需手动调用save if (self::where('varSlug', $user->varSlug)->where('id', '!=', $user->id)->exists()) { throw new \RuntimeException('用户标识重复'); } }); } - 优化校验逻辑:校验varSlug唯一性前,先确保varSlug已经生成有效值,不要用空值反复查库;如果是高并发场景,可以给varSlug字段加数据库唯一索引,从数据库层避免重复数据,减少应用层重复校验
- 你目前写的DB原生写入方法可以作为临时过渡方案,但长期必须修复Eloquent层的递归bug,否则项目其他位置调用User模型的create、update方法时仍然会触发同样的性能问题。
相关环境信息
- 运行环境:PHP 7.3.33、Laravel Framework 7.19.0
- 触发问题的待写入属性:
$attributes = [ 'name' => 'Guest '.$milliseconds, 'email' => "g_$milliseconds@my.com", 'phone' => "5555555555", 'birthday' => "01010000", 'my_role' => 3, 'password' => Hash::make('guest'), ];
内容的提问来源于stack exchange,提问作者randy
相关产品推荐
相关产品推荐

