Laravel 7队列触发1615数据库错误,求排查解决方案
调试Laravel 7共享主机上的SQLSTATE[HY000]: 1615错误
我太懂这种“功能没崩但错误日志天天跳”的烦躁了——咱们先把你的问题理清楚,再一步步找调试和解决的办法:
你的问题场景复盘
- Laravel 7部署在共享主机,仅靠一条Cron任务驱动调度:
* * * * * artisan schedule:run >> /dev/null 2>&1 - 没用到Supervisor,所以在
Console/Kernel.php里靠调度维持队列:- 每5分钟重启队列:
$schedule->command('queue:restart')->everyFiveMinutes(); - 每分钟启动守护态队列进程(防止重叠):
$schedule->command('queue:work --daemon')->everyMinute()->withoutOverlapping(); - 每日午夜运行自定义命令:
$schedule->command('myapp:mycommand')->daily();
- 每5分钟重启队列:
- 现象:
mycommand能正常执行,但约1小时后会触发两次1615错误,SQL语句是查询jobs表的预编译语句失效,错误栈指向Laravel核心的数据库连接类,目前任务仍能执行,但担心影响邮件等核心功能 - 已尝试的方案:
- 开启
ATTR_EMULATE_PREPARES:引发其他问题,放弃 - 服务商将
table_definition_cache提升至1024:初始24小时正常,现在每日触发超20次错误
- 开启
先搞懂1615错误的本质
这个错误是MySQL的经典问题:预编译语句失效,常见触发原因有几个:
- MySQL服务器重启、表结构被修改(哪怕是主机服务商那边的操作),导致缓存的表定义被清空
- 长生命周期的进程(比如你用的
--daemon队列进程)持有了旧的预编译语句,而MySQL的table_definition_cache不足,导致缓存被回收,旧语句无法复用 - 数据库连接断开后重连,但Laravel的进程没重新生成预编译语句
针对性调试方案
1. 给队列和调度加详细日志,抓触发时机
现在你只知道错误在mycommand运行1小时后出现,但不知道和队列重启、进程生命周期有没有关系,先加日志定位:
- 修改队列调度命令,输出详细运行日志:
$schedule->command('queue:work --daemon --verbose') ->everyMinute() ->withoutOverlapping() ->appendOutputTo(storage_path('logs/queue-worker.log')); - 给队列重启命令也加日志,确认它真的在按时执行:
$schedule->command('queue:restart') ->everyFiveMinutes() ->appendOutputTo(storage_path('logs/queue-restart.log')); - 查看日志里错误出现的时间,对比
queue-restart.log的重启记录、mycommand的执行时间,看有没有明显的时间关联(比如重启后多久出现错误?)
2. 排查队列进程的生命周期问题
你用了--daemon+withoutOverlapping(),理论上应该只有一个队列进程在跑,但共享主机的Cron调度可能有延迟或异常,先确认进程状态:
- 如果共享主机允许执行SSH命令,跑
ps aux | grep queue:work,看看有多少个队列进程在运行,以及它们的启动时间——如果有多个进程,或者某个进程已经跑了好几个小时,那就是withoutOverlapping()没生效,或者进程没被正常重启 - 尝试放弃
--daemon模式,改成让Cron每分钟启动一个新的队列进程,运行一段时间后自动退出:
这里$schedule->command('queue:work --timeout=300 --tries=3') ->everyMinute() ->withoutOverlapping();--timeout=300让进程运行5分钟后自动退出,刚好和你每5分钟的queue:restart配合,避免进程长时间持有旧的数据库连接和预编译语句
3. 监控数据库连接的状态变化
错误出在数据库连接层,我们可以监听Laravel的数据库查询事件,看错误发生前后的连接状态:
- 在
App\Providers\AppServiceProvider.php的boot()方法里加这段代码:
这样日志里会记录所有数据库操作,你可以看错误发生前有没有连接断开、重连的迹象,或者有没有其他修改use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; public function boot() { DB::listen(function ($query) { Log::info('DB Query', [ 'sql' => $query->sql, 'bindings' => $query->bindings, 'time' => $query->time, 'connection_name' => $query->connectionName, ]); }); }jobs表的操作
4. 针对预编译语句的临时调试
既然错误是预编译语句失效,你可以试试禁用数据库持久连接,避免旧连接持有失效的语句:
- 在
config/database.php的MySQL配置里,添加PDO::ATTR_PERSISTENT => false:'mysql' => [ // ... 其他配置 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), PDO::ATTR_PERSISTENT => false, // 禁用持久连接 ]) : [], ],
长期优化建议
- 尽量换掉MySQL队列驱动:如果共享主机支持Redis,把队列驱动改成Redis——Redis队列的实现不会用到MySQL的预编译语句,从根源上避免这个问题
- 联系服务商确认服务器操作:问问主机服务商,MySQL服务器有没有定期重启、缓存回收的操作,或者
table_definition_cache是否还有提升空间(虽然你已经升到1024,但共享主机的数据库可能是多用户共享,实际可用缓存可能不足) - 避免长生命周期进程:共享主机的环境不太适合跑长期守护进程,改成Cron每分钟启动一次短生命周期的队列进程,反而更稳定
内容的提问来源于stack exchange,提问作者kaarto
相关产品推荐
相关产品推荐

