后台配置页"双写"陷阱:当 `update_option` 遇上手动 `wp_cache_set` 后,我的设置开始"薛定谔生效"
上周给一个内部工具插件加配置页,踩了个挺隐蔽的坑——设置保存后,前台读到的值和后台显示的不一致,刷新一下又变,再刷新可能又回来。折腾一圈发现是自己手贱在配置保存逻辑里混用了两种缓存写法。
先贴下最初的问题代码,精简过的:
```php // 配置保存处理 public function save_settings() { if ( ! current_user_can( 'manage_options' ) ) { wp_die(); } check_admin_referer( 'my_plugin_save', 'my_plugin_nonce' ); $new_value = sanitize_text_field( $_POST['api_key'] ); // ① 写数据库 update_option( 'my_plugin_api_key', $new_value ); // ② 顺手刷新内存缓存(想着减少一次查询) wp_cache_set( 'my_plugin_api_key', $new_value, 'my_plugin', HOUR_IN_SECONDS ); // ③ 给前端回显用的瞬态缓存也更新 set_transient( 'my_plugin_api_key_cached', $new_value, HOUR_IN_SECONDS ); wp_redirect( admin_url( 'admin.php?page=my-plugin&saved=1' ) ); exit; } ```看起来没毛病?问题出在读取侧。另一个模块里我是这样读的:
```php public function get_api_key() { // 先查对象缓存 $cached = wp_cache_get( 'my_plugin_api_key', 'my_plugin' ); if ( false !== $cached ) { return $cached; } // 回退到 option $value = get_option( 'my_plugin_api_key' ); // 回填缓存 wp_cache_set( 'my_plugin_api_key', $value, 'my_plugin', HOUR_IN_SECONDS ); return $value; } ```坑来了:update_option 内部已经会更新对象缓存(非持久化缓存环境下是 alloptions 或单独 cache key,持久化环境下看你用的什么后端)。我手动又 wp_cache_set 一次,key 的命名空间/前缀对不上——WordPress 默认的 option 缓存 key 是 my_plugin_api_key 不带 group,或者带 options group(取决于 wp_load_alloptions 的加载策略),而我手写的 group 是 my_plugin。
结果是:
get_option读的是 WordPress 管理的缓存 → 新值 ✓wp_cache_get( 'my_plugin_api_key', 'my_plugin' )读的是我手写的缓存 → 可能旧值 ✗- 两个 key 在不同 group 里,互不相干,同一个数据有了两条缓存命
更骚的是,我用的 Redis Object Cache,flush 时机不一致。后台保存后重定向带 ?saved=1,页面渲染走 get_option 读到新值;但某个 AJAX 心跳或定时任务走了 wp_cache_get 那条线,拿到旧值,反手又把旧值写回瞬态缓存。用户体感就是"保存了,但一会儿变回去"。
我的修正方案:统一读写口径
方案 A(推荐):别在 option 上再套一层手动缓存,WordPress 的 option 缓存机制足够用了,除非你有特殊过期策略需求。
```php public function save_settings() { // ... 校验逻辑不变 $new_value = sanitize_text_field( $_POST['api_key'] ); update_option( 'my_plugin_api_key', $new_value ); // 只清不建,让下次读取自然回填 delete_transient( 'my_plugin_api_key_cached' ); wp_redirect( admin_url( 'admin.php?page=my-plugin&saved=1' ) ); exit; } public function get_api_key() { // 直接走 option,让 WordPress 自己管缓存层 return get_option( 'my_plugin_api_key', '' ); } ```方案 B(确实需要自定义缓存策略时):彻底绕过 update_option 的缓存机制,自己全包。
但方案 B 要处理 autoload、序列化、钩子触发,很烦,一般没必要。
一个验证技巧
怀疑缓存不一致时,可以用这段快速定位:
```php add_action( 'admin_init', function() { if ( ! isset( $_GET['debug_cache'] ) ) return; $opt = get_option( 'my_plugin_api_key' ); $cache_default = wp_cache_get( 'my_plugin_api_key' ); // 默认 group $cache_custom = wp_cache_get( 'my_plugin_api_key', 'my_plugin' ); wp_die( sprintf( 'option: %s | cache_default: %s | cache_custom: %s', var_export( $opt, true ), var_export( $cache_default, true ), var_export( $cache_custom, true ) ) ); } ); ```三个值对不上,就说明有"双写"或者 group 错配。
额外踩的半个坑
我一开始在保存后用 wp_cache_flush() 暴力清全局缓存当"修复",结果测试环境的其他插件配置也丢了,被运维追着骂。后来改成精准删除:
但最干净的还是别制造多份缓存。WordPress 的 option 系统不是裸数据库,它自带缓存语义,再套一层之前先想清楚到底要规避什么。
你们有没有在配置保存时遇到过"写入成功、读取失败"的玄学问题?最后发现是缓存层哪块的问题?