多数据库架构下必须为每个库绑定独立缓存store,否则Cache::get()因共用default store导致key冲突、数据错读;需配置redis_primary/redis_slave等专属store并显式调用Cache::store('name')->get()。

多数据库架构下直接复用单库缓存配置,会导致缓存 key 冲突、数据错读或静默失效;必须为每个数据库连接显式绑定独立缓存 store,并在查询时主动指定缓存驱动实例,不能依赖全局 Cache facade。
为什么默认 Cache::get() 会读错多库数据
ThinkPHP 的 Cache facade 默认走 default store,而多数据库场景中你可能有 mysql_primary 和 mysql_slave 两个连接,但缓存配置里只定义了一个 redis store —— 所有模型查询都往同一个 Redis 数据库(select => 0)写 key,比如都生成 think:user:1,根本无法区分来源。结果就是从从库查出的数据被主库操作覆盖,或反过来。
- 缓存 key 没有自动注入数据库连接标识,
Cache::set('user_1', $data)不知道该属于哪个库 -
config/cache.php中的stores.redis是单例,无法按 DB 连接动态切换 - 即使你手动改前缀(如
'prefix' => 'primary_'),也无法保证所有业务逻辑同步更新
如何为每个数据库连接配专属缓存 store
核心是绕过全局 Cache,用 Cache::store() 拿到指定 store 实例,再结合数据库连接名做语义隔离。不改框架源码,纯配置+调用约定即可落地。
- 在
config/cache.php的stores数组里新增两个 Redis store:redis_primary和redis_slave,各自配不同select值(如0和1)和prefix(如'primary:'和'slave:') - 确保两个 store 的
type都是'redis',且参数(host、port、password)一致,仅隔离逻辑库和前缀 - 不要动
default配置项,它仍可作为通用缓存(如配置项、菜单)使用,与 DB 缓存解耦 - 在模型或 service 中,显式调用
Cache::store('redis_primary')->get('user_1'),而非Cache::get('user_1')
避免手写 store 名导致拼写错误
硬编码 'redis_primary' 容易打错,且不利于后期切换驱动。建议封装一层轻量路由:
立即学习“PHP免费学习笔记(深入)”;
// app/common/DbCache.php
<?php
class DbCache
{
public static function primary()
{
return Cache::store('redis_primary');
}
public static function slave()
{
return Cache::store('redis_slave');
}
}
- 调用时写
DbCache::primary()->set('order_123', $data, 3600),语义清晰、IDE 可跳转、重构安全 - 若某天要把从库缓存换成 file 驱动,只需改
slave()方法返回Cache::store('file_slave'),业务层零修改 - 注意:该类不注册为服务容器对象,避免生命周期干扰;纯静态门面更轻量
缓存失效策略必须匹配读写分离逻辑
主库写后,不能只删 redis_primary 的 key,否则从库查询仍可能命中旧缓存。必须双删,且顺序不能反。
- 写操作(如更新用户)完成后,先删
DbCache::primary()->delete('user_1'),再删DbCache::slave()->delete('user_1') - 不能用
flush或通配符删除,会误伤其他 key;必须精准 key 级别操作 - 如果用了
Cache::remember(),它的回调函数里必须也指定 store,例如DbCache::slave()->remember('user_1', 3600, function () { ... }) - Redis 的
select隔离只是逻辑分库,不是物理隔离;生产环境建议用不同 Redis 实例,select仅用于开发/测试快速验证
最易被忽略的是缓存键的生成逻辑 —— 如果你在模型里用 $this->name . '_' . $this->id 拼 key,而模型同时被主从库共用,那 key 字符串完全一样,前缀和 store 隔离就白做了。务必确认 key 构造过程已嵌入数据库连接上下文,或统一走 DbCache 封装层收口。



















