Laravel中如何处理并发提交请求避免SQL Server数据库随机报错
Laravel站点并发Excel导入SQL Server随机报错修复方案
核心排查与修复步骤
先抓明确的错误信息,不要盲目调整
直接查两处日志定位根因:- Laravel日志路径:
storage/logs/laravel.log,找报错时的SQL错误码、具体SQL语句 - SQL Server端执行以下语句抓最近的死锁、锁超时记录:
SELECT * FROM sys.dm_tran_deadlocks SELECT * FROM sys.dm_os_wait_stats WHERE wait_type LIKE '%LOCK%'
常见错误码对应定向修复方向:
- 1205:死锁,属于事务锁冲突
- 1222:锁等待超时,锁粒度过大
- 08001/08S01:数据库连接异常,连接配置或网络问题
- 2627/2601:唯一键冲突,并发写入重复数据
- Laravel日志路径:
修复事务与锁冲突问题(占这类并发报错的80%以上)
- 缩短事务生命周期:绝对不要把Excel解析、文件读取、数据校验这类IO/计算逻辑放进数据库事务里,事务里只保留最终的批量写入操作,单事务执行时间控制在1秒以内
- 控制批量写入粒度:不要把整份Excel的几万行数据放进单个事务一次性写入,拆分单批次100-500行分批upsert,避免SQL Server自动触发锁升级(行锁升级为表锁)
- 优化锁粒度:写入查询中添加
WITH (ROWLOCK)提示,强制使用行级锁;给导入查询的筛选字段加非聚集索引,避免全表扫描加大范围锁 - 禁止全表删写逻辑:不要用
truncate/全表delete+全量insert的方式做导入,改用按主键/唯一键upsert的增量写入逻辑
修复数据库连接配置问题
- 打开
config/database.php,找到sqlsrv连接配置,将persistent参数设为true,开启持久连接,避免并发请求频繁新建连接耗尽数据库连接配额 - 如果站点用了Octane/Swoole/RoadRunner加速,必须配置SQL Server连接池,连接池大小设置为EC2实例CPU核数的2-4倍,禁止连接池大小超过数据库端的最大连接限制
- 检查EC2安全组、SQL Server端的连接超时配置,将空闲连接超时设为300秒以上,避免大文件导入过程中连接被中间设备掐断
- 打开
重构导入逻辑避免资源抢占
- 把同步HTTP请求里的导入逻辑全部迁移到Laravel队列异步执行,用户上传文件后直接返回导入中提示,后台排队跑任务,避免长时间占着PHP进程和数据库连接
- 给同一张目标表的导入任务加分布式互斥锁,同一时间只允许一个任务写入同一张表,避免硬锁冲突:
// 导入任务执行前加锁,锁超时时间设为单任务最大执行时长即可 $lock = Cache::lock('import_lock_' . $targetTableName, 600); if (!$lock->get()) { // 锁获取失败直接返回提示,不要排队等锁 throw new \Exception('当前有其他导入任务正在执行,请5分钟后再试'); } try { // 执行分批导入逻辑 } finally { $lock->release(); } - 导入专用的队列worker数量不要设置过高,单台EC2实例配置2-4个worker即可,避免并发写入打满数据库IOPS
验证方式
调整完成后,用压测工具模拟2-5个用户同时上传不同Excel文件触发导入,连续压测20次以上无报错,且数据库监控中无死锁、锁等待数量突增的情况即为修复完成。
内容的提问来源于stack exchange,提问作者Kiran Dalawai
相关产品推荐
相关产品推荐

