Laravel切换数据库缓存驱动后出现主键重复错误求助
问题描述
将缓存驱动切换为数据库后,Laravel持续抛出如下错误,目前已产生数千条:
PDOException: Uncaught PDOException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'dbapp_cache_9794540492d50c5a53832f6e31d7ca3fced8f2d2:timer' for key 'cache.PRIMARY' in /app/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:117
我已经禁用了所有用到缓存的路由,但错误还是出现,不清楚Laravel到底在缓存什么。缓存表里全是这类带时间戳的记录。
更新1:切换驱动后的迁移操作
切换缓存驱动后执行了以下迁移代码:
Schema::create('cache', function (Blueprint $table) { $table->string('key')->primary(); $table->mediumText('value'); $table->integer('expiration'); }); Schema::create('cache_locks', function (Blueprint $table) { $table->string('key')->primary(); $table->string('owner'); $table->integer('expiration'); }); Schema::table('forms', function (Blueprint $table) { $table->index('user_id'); }); Schema::table('respondents', function (Blueprint $table) { $table->index('deleted_at'); }); Schema::table('subscriptions', function (Blueprint $table) { $table->index('gateway_id'); });
更新2:当前唯一使用缓存的场景
目前代码里唯一用到缓存的地方是数据库锁:
return Cache::lock("subscription_{$subId}")->block(10, function() use($request, $subId){ // 业务代码... });
问题根源与解决建议
核心原因
这个错误是缓存锁的定时器键并发插入冲突导致的,具体来说:
- 缓存锁内部机制:Laravel的
Cache::lock()->block()方法会自动生成带:timer后缀的缓存键,用来跟踪锁等待的超时时间。当多个请求同时争抢同一个锁(比如同一个subscription_{$subId})时,都会尝试创建这个:timer键。 - 数据库驱动的非原子性:数据库缓存驱动的
add操作是“先查后插”的非原子逻辑——高并发下,多个请求会同时通过“键不存在”的校验,然后同时执行插入,直接触发主键重复的约束错误。 - 潜在表结构问题:虽然不是直接原因,但如果
cache表的key字段长度不够(比如默认string是255字符),可能导致长键被截断,间接引发重复;不过从错误信息看,你的键是完整的,这个可能性较低。
解决方法
- 优先换用Redis驱动:Redis的
SETNX命令是原子性的,能从根源避免这种并发冲突,这也是Laravel缓存锁推荐使用的驱动。 - 重写数据库缓存驱动的
add方法:如果必须用数据库驱动,可自定义驱动,把add操作改成INSERT ... ON DUPLICATE KEY UPDATE的原子逻辑,避免重复插入。 - 清理现有冲突缓存:先清空
cache表中所有带:timer后缀的键,暂时缓解错误。
内容的提问来源于stack exchange,提问作者user3152263
相关产品推荐
相关产品推荐

