Laravel 8 Rate Limiter缓存表重复键违反唯一约束问题排查
问题根因定位
这个问题绝大多数场景属于配置遗漏,少部分是Laravel 8.x低版本的已知框架问题,不属于核心设计缺陷,具体原因如下:
- 限流键未做唯一分片
Laravel 8的速率限制器默认不会自动添加请求主体的唯一标识,如果你在定义RateLimiter规则时没有调用by()方法指定分片维度,所有请求会共用同一个限流缓存键,高并发下多个请求同时写入同一个键就会触发冲突。
比如以下错误配置会导致全局共用同一个键:
// 错误配置:没有指定分片规则,所有请求共用同一个键 RateLimiter::for('api', function (Request $request) { return Limit::perMinute(60); });
- Database缓存驱动的非原子写入缺陷
Laravel 8.83.0之前的版本,database缓存驱动的写入逻辑是「先查询键是否存在,不存在则插入、存在则更新」的两步非原子操作,PostgreSQL的唯一键约束会在并发写入同一个不存在的键时,拦截到同时执行的插入请求,抛出重复键错误。
修复方案
- 补全限流键分片规则
在限流规则中加入用户/IP维度的分片标识,确保每个访问主体的限流键唯一:
// 正确配置:按用户ID/IP分片,每个主体的键独立 RateLimiter::for('api', function (Request $request) { return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); });
如果业务本身需要全局共用同一个限流键,跳过该步骤执行后续修复。
- 升级Laravel 8小版本
升级到Laravel 8.83.0及以上版本,该版本已适配PostgreSQL的INSERT ... ON CONFLICT原子写入语法,从框架层面解决database缓存驱动的并发写入冲突问题。 - 替换为Redis缓存驱动
限流场景优先使用Redis作为缓存驱动,Redis单线程的写入机制天然支持原子操作,不会出现这类数据库级别的唯一键冲突,同时性能也远高于database缓存。
内容的提问来源于stack exchange,提问作者DOlson
相关产品推荐
相关产品推荐

