后台配置页"保存后刷新却回显旧值":我排查三天发现是 `update_option` 与对象缓存的"时间裂缝"
上周给插件加后台配置页,表单提交、数据库落库、成功提示全绿,但 F5 一刷新,页面回显的仍是老数据。Chrome 控制台干净得像没发生过任何事,数据库里新值明明在。折腾到第三天凌晨,才意识到是对象缓存(object cache)在搞"影分身"。
先把当时的简化代码贴出来,问题就藏在第 7 行:
public function save_settings() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( '权限不足' );
}
check_admin_referer( 'my_plugin_save', 'my_plugin_nonce' );
// 用户勾选"启用高级模式",值为 1
$advanced_mode = isset( $_POST['advanced_mode'] ) ? 1 : 0;
update_option( 'my_plugin_advanced_mode', $advanced_mode ); // ← 这里"成功"了
// 立刻回写,用于同一次请求内的后续逻辑
$this->settings['advanced_mode'] = $advanced_mode;
add_settings_error(
'my_plugin_messages',
'my_plugin_message',
'设置已保存',
'updated'
);
}
看起来人畜无害对吧?`update_option` 返回 true,数据库 `wp_options` 表里值也变了。但配置页的回显用的是 `get_option('my_plugin_advanced_mode')`,这个调用在 Memcached/Redis 开启的环境下,**不会**去读数据库,而是读对象缓存里的副本。而 `update_option` 的源码里有个细节:
// wp-includes/option.php 简化逻辑
function update_option( $option, $value, $autoload = null ) {
// ... 前置检查 ...
$result = $wpdb->update( $wpdb->options, ... );
if ( ! wp_installing() ) {
$notoptions = wp_cache_get( 'notoptions', 'options' );
if ( is_array( $notoptions ) && isset( $notoptions[$option] ) ) {
unset( $notoptions[$option] );
wp_cache_set( 'notoptions', $notoptions, 'options' );
}
wp_cache_set( $option, $value, 'options' ); // ← 这里会刷新缓存
}
return $result;
}
理论上 `wp_cache_set` 会覆盖缓存,那为啥我的场景失效了?因为我用了自定义的缓存分组。插件早期为了"性能优化",把配置项塞进了自己的 group:
// 错误示范:自以为聪明的"优化"
wp_cache_set( 'my_plugin_settings', $settings, 'my_plugin_group', 3600 );
保存时只调了 `update_option`,却忘了同步清理 `my_plugin_group` 里的聚合缓存。于是数据库是新值,对象缓存是老值,页面回显从缓存读,用户看到的就是"保存失败"的假象。更坑的是,本地开发环境没开对象缓存,这个问题在线上才暴露。
现在的做法是分层管理,统一出口:
class My_Plugin_Settings {
private const CACHE_GROUP = 'my_plugin';
private const CACHE_KEY = 'settings_v2'; // 加版本号,方便强制失效
public function get( $key, $default = null ) {
$settings = wp_cache_get( self::CACHE_KEY, self::CACHE_GROUP );
if ( false === $settings ) {
$settings = get_option( 'my_plugin_settings', [] );
wp_cache_set( self::CACHE_KEY, $settings, self::CACHE_GROUP, 600 );
}
return $settings[$key] ?? $default;
}
public function set( $key, $value ) {
$settings = $this->get_all(); // 先读当前全量
$settings[$key] = $value;
// 数据库与缓存必须原子性处理
$updated = update_option( 'my_plugin_settings', $settings );
if ( $updated ) {
wp_cache_set( self::CACHE_KEY, $settings, self::CACHE_GROUP, 600 );
}
return $updated;
}
public function flush() {
wp_cache_delete( self::CACHE_KEY, self::CACHE_GROUP );
}
}
另外给保存按钮加了"硬刷新"的兜底:提交成功后带一个 `?my_plugin_flush=1` 参数,后端检测到就执行一次 `wp_cache_flush()` 的定向清理(不是全站 flush,只清自己的 group)。虽然有点粗暴,但在复杂托管环境(比如某些强制缓存 1 小时的平台)里能救命。
最后列几个排查这类问题的快捷命令,WP-CLI 在手会快很多:
# 看某个 option 的当前缓存值
wp cache get my_plugin_settings options
# 强制删除,测试是否缓存导致
wp cache delete my_plugin_settings options
# 直接读数据库绕过缓存
wp db query "SELECT option_value FROM wp_options WHERE option_name='my_plugin_settings'"
对象缓存不是洪水猛兽,但"半吊子优化"比不用还危险。如果你也遇到过保存成功、回显失败的玄学问题,先问问自己:数据到底存在几份?哪一份才是"真相"。

