codeigniter 4 数据库读写分离不自动同步 redis 缓存,必须手动协调写操作后的缓存更新(如 $redis->del() 或 $redis->set())与读操作的缓存穿透防护(先查缓存、未命中再查库并回填),同时注意 redis cluster 需替换为 predis 驱动、key 设计需含版本或时间戳以规避主从延迟导致的脏读。

CodeIgniter 4 的数据库读写分离本身不自动感知 Redis 缓存状态,必须手动协调——否则容易出现「刚写完查不到」或「缓存没更新导致脏读」。
CI4 中 $db->query() 和 $db->insert() 不触发 Redis 自动同步
CI4 的数据库类只负责 SQL 执行,完全不关心缓存层。哪怕你用 $redis->set('user_123', $data) 手动写了缓存,后续 $db->update() 也不会自动删或刷新对应 key。
- 所有写操作(
insert/update/delete)后,必须显式调用$redis->del('key')或$redis->set('key', $fresh_data) - 读操作不能无脑走缓存:先
$redis->get('key'),命中则返回;未命中才走$db->get()->getResult(),并立刻$redis->set('key', $result) - 注意 TTL 设置:缓存过期时间要略大于主从复制延迟(如从库延迟 200ms,TTL 至少设为 1s),否则可能刚写完就过期,又去读旧从库
读写分离配置里 read 连接不等于「只读缓存」
CI4 的 database.php 中配置了 'read' 和 'write' 组,只是把 SELECT 转发到从库、INSERT/UPDATE 走主库——它和 Redis 完全无关。Redis 是独立缓存层,需在业务逻辑中桥接。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
-
'read'组的从库仍可能有复制延迟,所以不能依赖它保证强一致性;Redis 缓存反而更及时(只要你写后主动刷新) - 不要在
read连接上做缓存穿透防护:比如用户查user_1000000,从库查不到 → 返回 null → 缓存也写NULL(防穿透),这个逻辑得自己写,CI4 不提供 - 如果用了 Redis Pipeline 批量读,记得
$redis->pipeline()->get(...)->get(...)->exec(),别漏掉exec(),否则命令不发出去
redis-cli -c 连 Cluster 模式时,CI4 的 RedisHandler 默认不支持
CI4 自带的 RedisHandler(用于 session 或 cache 驱动)只支持单机或 Sentinel,不识别 Redis Cluster 的哈希槽路由。直接配 host:port 连 Cluster 实例会报 MOVED 或 ASK 错误。
- 必须换底层客户端:改用
predis/predis或phpredis扩展,并在 CI4 配置中指定'driver' => 'predis' - Cluster 连接字符串格式是
tcp://192.168.1.10:7000?timeout=2.5&read_timeout=2.5&retry_interval=0.1,不是单个 host+port -
redis-cli -c -h 192.168.1.10 -p 7000能连通 ≠ CI4 应用能连通,后者需要驱动层支持重定向
缓存 key 设计不当会导致读写分离失效
如果 key 命名只含业务 ID(如 'user_123'),而没带上「数据来源标识」,就无法区分该缓存是来自主库还是从库——尤其在主从延迟期间,用户刚提交订单,立刻查「订单列表」缓存,结果拿到的是旧快照。
- 建议 key 加版本或时间戳前缀:
'v2:user_123'或'ts1722951120:user_123',写操作后更新版本号,强制刷新 - 对列表类查询(如
SELECT * FROM orders WHERE user_id=123),不要缓存整个结果集,而是缓存'orders_user_123_ids'(ID 列表),再用 pipeline 并行查详情,避免大 value 占内存 - CI4 的
Cache类默认使用文件驱动,切到 Redis 前务必检查app/Config/Cache.php中'handler' => 'redis'和'redis' => [...]是否完整配置
真正麻烦的不是配置多,而是每个写操作都要想「这里有没有缓存?要不要删?删哪个 key?有没有并发写导致覆盖?」——这些没法靠框架自动兜底,得在 service 层用明确的缓存策略兜住。










