不能,redis setex只接受字符串值,php对象需先serialize()序列化再存,读取时unserialize()还原;含闭包、资源等无法序列化,建议优先用json替代。

Redis setex 能直接存 PHP 对象吗?不能,必须序列化
PHP 的 setex 只接受字符串作为值,传入数组、对象或资源会静默转成 Array 或触发警告。不显式序列化就写入,取出来就是无意义的字符串,反序列化也会失败。
正确做法是用 serialize() 编码后再存,读取时用 unserialize() 还原。PHP 8.1+ 更推荐 igbinary_serialize()(需扩展),但默认环境优先用原生函数。
-
setex第二个参数是过期秒数,别误写成时间戳 - 对象若含闭包、资源或未定义类,
serialize会失败或丢失数据 - 数据库查询结果建议先用
json_encode($data, JSON_UNESCAPED_UNICODE)+json_decode($str, true)替代序列化——更安全、跨语言、可读性强
缓存键设计要带业务上下文,避免冲突
只用 SQL 语句哈希作 key 看似方便,但相同 SQL 在不同用户、分页参数、状态条件下应返回不同结果。硬编码 key 如 "user_list" 更危险,极易覆盖。
推荐组合:业务前缀 + 参数签名。例如用户列表按角色分页:
$key = 'cache:user:list:role_' . $role_id . '_page_' . $page;
注意:$role_id 和 $page 必须过滤为整型或白名单字符串,防止 key 注入(如传入 $page = "1&limit=999" 导致 key 污染)。
- 不要在 key 中拼接原始 SQL,长度不可控且含空格/特殊字符需额外
urlencode - Redis key 长度建议控制在 255 字节内,过长影响性能和集群分片
- 开发期可用
redis-cli --scan --pattern 'cache:user:*'快速验证 key 是否符合预期
读缓存 → 查库 → 写缓存,必须加原子性兜底
经典“先查缓存,未命中再查 DB 并写缓存”流程,在高并发下可能引发缓存击穿或重复写入。尤其当多个请求同时发现缓存失效,会集体打到数据库。
稳妥做法是:查缓存失败后,用 setnx 尝试占位,成功者查库并写缓存;失败者短暂休眠后重试读缓存。
$key = 'cache:user:profile:' . $uid;
$data = $redis->get($key);
if ($data === false) {
// 尝试设锁,10秒过期防死锁
$lockKey = $key . ':lock';
if ($redis->setnx($lockKey, 1) && $redis->expire($lockKey, 10)) {
$data = $pdo->query("SELECT * FROM users WHERE id = ?")->fetch();
if ($data) {
$redis->setex($key, 3600, serialize($data));
}
$redis->del($lockKey);
} else {
// 等待 50ms 后再读一次,避免全量等待
usleep(50000);
$data = $redis->get($key);
}
}
-
setnx+expire不是原子操作,Redis 2.6.12+ 应改用set($key, $val, ['nx', 'ex' => 10]) - 休眠时间不宜过长,否则拖慢响应;也不宜为 0,否则可能无限循环
- 若业务允许脏读,可跳过锁机制,改用「逻辑过期」:缓存值里存
['data' => ..., 'expire_time' => time() + 3600]
反序列化失败时要有降级逻辑,不能让页面报错
缓存内容被手动篡改、Redis 数据损坏、PHP 版本升级导致序列化格式不兼容,都可能使 unserialize() 返回 false 或抛出异常。没捕获就会中断整个请求。
必须用 try/catch 包裹,并回退到数据库查询:
$data = $redis->get($key);
if ($data !== false) {
try {
$data = unserialize($data);
if ($data === false && !is_null($data)) {
throw new Exception('unserialize failed');
}
} catch (Exception $e) {
error_log("Cache unserialize error for key {$key}: " . $e->getMessage());
$data = null; // 触发后续查库
}
}
if ($data === null) {
$data = $pdo->query("SELECT ...")->fetch();
$redis->setex($key, 3600, serialize($data));
}
- 不要依赖
unserialize的返回值类型判断是否成功——它可能返回0、空数组等合法值 - 生产环境禁用
unserialize处理不可信数据,缓存内容若来自外部输入,务必改用json方案 - 记录反序列化失败日志时带上
$key和strlen($data),便于排查是截断还是编码错误
缓存不是开关,而是需要校验、兜底、监控的中间层。最常被忽略的是 key 的动态性和反序列化的脆弱性——这两点一旦出问题,往往表现为偶发空白页或数据错乱,排查成本远高于初期多写几行防御代码。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











