codeigniter原生cache_on()不支持参数化查询区分、无ttl控制、不兼容redis且缓存键易冲突,需自定义封装带键管理的查询缓存类,核心是拦截sql执行、生成含参数的唯一缓存键、支持redis后端及同步失效机制。

CI(CodeIgniter)本身不提供数据库查询结果的持久化缓存封装,必须自己动手补全——不是配个 config 就能用,而是要重写查询执行路径、接管结果存储与读取逻辑。
为什么不能直接用 $this->db->cache_on()?
CI 原生的文件缓存(cache_on)只支持简单 SELECT 查询,且缓存键由 SQL 字符串 MD5 生成,无法区分参数化查询中的不同值(比如 WHERE id = ? 绑定 1 和 5 会共用同一缓存),极易命中脏数据。更关键的是:它不支持 Redis/Memcached 等后端,也不支持 TTL 控制、缓存穿透防护或主动失效。
- 缓存键未包含绑定参数,导致参数不同但 SQL 相同的查询互相污染
- 缓存目录权限错误时静默失败,查不到日志也无报错
- 不支持异步写入或批量刷新,高并发下可能写冲突
如何封装一个带键控制的查询缓存类?
核心是绕过 CI 的 Active Record 执行链,在真正发 SQL 前拦截、计算唯一缓存键、尝试读缓存;若未命中,执行查询并写入缓存。推荐继承 CI_DB_query_builder 或新建 CacheableDB 类:
- 缓存键必须包含:表名 + WHERE 条件字段名及值(序列化后哈希)、ORDER/LIMIT 参数、是否含 JOIN(可加标志位)
- 使用
md5(json_encode($cache_context))生成键,比纯 SQL 更稳定 - 写缓存前检查结果集大小,避免缓存超大数组(如
count($result) > 1000时跳过) - 强制要求所有缓存操作走统一入口,例如
$this->cache_db->get_cached('users', ['status' => 'active'])
Redis 作为后端时要注意哪些坑?
CI 默认不带 Redis 驱动,需手动加载 redis 库,并确保 redis 扩展已启用。缓存值必须序列化,但 PHP serialize() 在不同版本间兼容性差,建议改用 json_encode() + JSON_UNESCAPED_UNICODE。
-
setex命令设 TTL,别用set+expire两步,否则可能缓存写入成功但过期失败 - Redis 连接异常时必须 fallback 到直连数据库,否则整个页面 500
- 缓存键长度限制(Redis 默认 512MB,但实际建议 ≤ 1KB),长键需截断+加校验码
- 慎用
hGetAll批量读,易触发慢查询;按需用mGet多键获取
缓存失效怎么设计才不丢数据?
不能只靠过期时间。写操作(INSERT/UPDATE/DELETE)发生时,必须同步清理相关缓存。最简方案是约定规则:操作 users 表时,删除所有以 cache_users_* 开头的键。
- 用
SCAN+DEL组合清理前缀(Redis 6.0+ 支持UNLINK避免阻塞) - 在 Model 的
update()方法末尾加$this->redis->del("cache_users_{$id}"); - 对列表类缓存(如分页),失效粒度设为“表级”而非“行级”,否则更新一条就清空整页
- 事务中执行写操作时,缓存清理必须放在事务提交之后,否则回滚会导致缓存与 DB 不一致
真正的难点不在写缓存,而在判断什么时候不该缓存——比如用户登录态、实时库存、支付状态这类数据,缓存一毫秒都可能出问题。封装再漂亮,也得靠人来权衡业务语义。











