后台配置页"保存成功但前端不生效":我追踪到 `update_option` 与 `wp_cache_set` 之间那条"幽灵通道"
上周给插件加"全局开关"功能,后台保存提示"设置已更新",刷新页面回显也是新值,但前端模板读取到的还是老数据。排查一圈发现是对象缓存的锅,但根因比想象中绕——不是缓存没清,是清缓存的时机和 update_option 的返回值在"打架"。
现象还原:保存流程里的"时间裂缝"
我的配置页大概长这样(简化版):
// 保存逻辑
if ( isset( $_POST['my_plugin_save'] ) ) {
check_admin_referer( 'my_plugin_settings' );
$new_value = sanitize_text_field( $_POST['toggle_switch'] );
$updated = update_option( 'my_plugin_toggle', $new_value );
// 想当然地:保存成功就刷缓存
if ( $updated ) {
wp_cache_delete( 'my_plugin_toggle', 'options' );
add_settings_error( 'my_plugin', 'saved', '设置已更新', 'updated' );
}
}
问题就卡在 $updated 这个返回值。查文档:update_option 返回 false 有两种情况——更新失败,或者新值与数据库现有值相同。对象缓存里存的是旧值,但数据库里可能已经被其他进程/手动改过,此时 update_option 发现"没变化",返回 false,我的缓存清理逻辑直接跳过。
更坑的是:如果对象缓存用的是 Redis/Memcached,update_option 内部其实会调 wp_cache_set 写缓存,但写完之后我的代码又手动 wp_cache_delete——这看似多此一举,实则是在多实例部署时(比如容器化环境),其他 Pod 的缓存实例根本没被通知到。
拆成两步:先强制写,再广播清
现在的做法是抛弃"返回值判断",改为无条件刷新,但用 wp_cache_replace 做乐观锁避免并发覆盖:
public function save_toggle( string $new_value ): void {
$old_value = get_option( 'my_plugin_toggle' ); // 触发缓存读取
// 1. 直接写库,不依赖返回值判断
update_option( 'my_plugin_toggle', $new_value );
// 2. 强制刷新本实例缓存(即使值相同也执行)
wp_cache_delete( 'my_plugin_toggle', 'options' );
wp_cache_delete( 'alloptions', 'options' ); // 注意这个!
// 3. 多实例环境:发"缓存失效"信号
do_action( 'my_plugin_cache_invalidate', 'toggle', $new_value, $old_value );
}
第三个 wp_cache_delete( 'alloptions', 'options' ) 是暗雷。get_option 有"自动加载"机制,所有 autoload=yes 的选项会被打包进 alloptions 缓存键。单独删 my_plugin_toggle 不够,get_option 可能从 alloptions 里读打包好的旧数据。
钩子联动:让"配置变更"变成显式事件
后端模板读取统一走封装方法,内部挂缓存钩子:
public function get_toggle(): bool {
$cache_key = 'my_plugin_toggle_rendered';
$cached = wp_cache_get( $cache_key );
if ( false !== $cached ) {
return (bool) $cached;
}
$raw = get_option( 'my_plugin_toggle', 'off' );
$parsed = $this->parse_toggle( $raw ); // 可能涉及多选项合并
wp_cache_set( $cache_key, $parsed, '', MINUTE_IN_SECONDS * 5 );
return $parsed;
}
保存时除了清选项缓存,还要触发"派生缓存"失效:
do_action( 'my_plugin_cache_invalidate', 'toggle', $new_value, $old_value );
// 在初始化时注册
add_action( 'my_plugin_cache_invalidate', function( $key ) {
if ( 'toggle' === $key ) {
wp_cache_delete( 'my_plugin_toggle_rendered' );
wp_cache_delete( 'my_plugin_toggle' );
}
}, 10, 1 );
踩坑备忘
update_option的$autoload参数在第一次创建选项时生效,后续更新不会改。如果后期从"不自动加载"切到"自动加载",得先delete_option再重建。- WP-CLI 执行
wp option update时不会走你的后台表单逻辑,缓存清理钩子如果注册在admin_init里会完全漏掉。建议把缓存清理绑在updated_option通用钩子上。 - 多站点环境下,
update_blog_option和switch_to_blog会让缓存键前缀乱跳,封装时最好用wp_cache_add_global_groups显式声明。
现在我的配置页保存后,前后端读值终于一致了。核心教训:别把 update_option 的返回值当"数据变更确认书",在缓存层面前,它只能告诉你"数据库写操作的状态",而你要的是"全链路数据一致性"。
你们是怎么处理配置保存后的缓存同步的?有没有遇到过 alloptions "幽灵打包"导致个别选项失效不生效的情况?

