后台配置页"保存成功但前端不生效":我追踪到 `update_option` 与 `wp_cache_set` 之间那条"幽灵通道"

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
插件开发 71 浏览 0 回复

上周给插件加"全局开关"功能,后台保存提示"设置已更新",刷新页面回显也是新值,但前端模板读取到的还是老数据。排查一圈发现是对象缓存的锅,但根因比想象中绕——不是缓存没清,是清缓存的时机和 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_optionswitch_to_blog 会让缓存键前缀乱跳,封装时最好用 wp_cache_add_global_groups 显式声明。

现在我的配置页保存后,前后端读值终于一致了。核心教训:别把 update_option 的返回值当"数据变更确认书",在缓存层面前,它只能告诉你"数据库写操作的状态",而你要的是"全链路数据一致性"。

你们是怎么处理配置保存后的缓存同步的?有没有遇到过 alloptions "幽灵打包"导致个别选项失效不生效的情况?

评论0
回复 · 0
还没有回复
微信客服 微信客服