Laravel高并发下数据库/Redis连接泄漏主因是长驻进程复用异常连接,导致事务挂起、锁等待或连接数超限;需通过独立连接配置、优雅重启和原子锁机制切断复用路径。

高并发下 Laravel 未正确关闭数据库或 Redis 连接,最常表现为连接数持续上涨、出现 Too many connections 或 Connection refused 错误,根本原因不是“忘了关”,而是连接生命周期被长驻进程(如队列 worker、Swoole)复用时,异常中断导致连接处于半打开/事务挂起状态。
DB::transaction() 在队列里不回滚的真正原因
这不是事务没写对,而是连接实例被复用后“带病上岗”:上一个任务抛出未捕获异常,DB::rollback() 没执行,连接就卡在 IN TRANSACT 状态。下一个任务调用 DB::transaction() 时,PDO 直接报 SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active。
- 别依赖 try-catch 包裹整个事务——异常可能发生在闭包外(比如日志写入失败、中间件崩溃)
-
DB::transaction()内部只保证闭包内逻辑的原子性,不保证连接本身的清理 - MySQL 的
innodb_lock_wait_timeout控制的是锁等待超时,不是连接空闲超时;PDO::ATTR_TIMEOUT只管建连,不管事务卡住 - 验证方式:在队列任务中故意 throw Exception,然后查
SHOW PROCESSLIST,会看到状态为Locked或Waiting for table metadata lock的连接
强制重置连接的三种实操方式
核心思路是不让“脏连接”流入后续任务。不能靠 PHP 层 finally 主动 close(Laravel 不暴露底层 PDO 实例),得从连接管理策略入手:
- 对关键队列任务,显式指定独立连接:
DB::connection('pgsql_write')->transaction(...),并在config/database.php中为该连接配置'sticky' => false和'options' => [PDO::ATTR_EMULATE_PREPARES => true] - 在 Supervisor 配置中加
stopwaitsecs=10和autorestart=true,确保 worker 异常退出后连接被 OS 回收(比 PHP 主动 close 更可靠) - 使用
php artisan queue:restart触发优雅重启 —— 它会向所有 worker 发送 SIGTERM,worker 收到后会在当前任务结束后释放连接并退出,避免 abrupt kill 导致连接泄漏
Redis 连接未释放的典型场景与修复
Redis 连接泄漏往往藏在 Cache::lock() 或手动 Redis::setex() 后没释放,尤其在 catch 块里忘记 finally 调用 Redis::del()。
-
Cache::lock('key', 30)->block(5)->get()是安全的:它内部用Redis::eval()原子执行加锁+设置过期,且闭包结束自动释放 - 但手写
Redis::set('lock:key', '1', 'EX', 30, 'NX')必须配try-finally,否则任何异常(包括内存溢出、超时 kill)都会导致锁 key 永久残留 - FPM 环境下用 PhpRedis 时,确认
config/database.php的 redis 配置含'persistent' => true,否则每次请求新建连接,高并发下直接打满max_connections - Swoole 环境下,禁用
Redis::connect(),改用Redis::pconnect()或协程池工厂,避免每个协程都建新连接
连接是否“正确关闭”,不取决于你写了多少 close(),而取决于你是否切断了它被复用的路径。队列 worker 和 Swoole 进程的生命周期远长于单次请求,连接复用是常态,异常处理必须按“连接可能被污染”来设计,而不是按“用完即弃”来假设。


















