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

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唯一性校验,累计出上万条重复查询。

排查步骤

  1. 打开User模型文件,逐一检查上述提到的事件、观察器、修改器、全局作用域代码,重点定位所有和varSlug字段生成、唯一性校验相关的逻辑
  2. 找到重复调用save、重复触发查询的递归点:可以临时在唯一性校验的代码位置打印debug_backtrace()看调用栈,能直接定位是哪段代码在反复触发查询
  3. 检查是否存在重复挂载校验逻辑的问题:比如同时在模型事件、表单验证、观察器里写了相同的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 10:51:21