`wp_insert_post` 后立刻 `get_post_meta` 返回空数组:我踩了 "事务隔离" 与 "钩子时序" 的双重陷阱

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

上周写个内容同步插件,逻辑贼简单:A 站发文 → 调 REST API 写到 B 站 → 立刻回写 B 站的 source_id 到 A 站做关联。结果 wp_insert_post 返回了正确的 $post_id,紧接着的 update_post_meta 也报成功,但下一秒 get_post_meta($post_id, 'source_id', true) 死活返回空。

更诡异的是:手动刷新页面,值出来了;加个 sleep(1),值也出来了。我第一反应是对象缓存作妖,wp_cache_flush() 抡了好几遍,没用。

❌ 错误写法:把"同步完成"当成了"数据落盘"

// 在 save_post 钩子里直接开干
add_action( 'save_post', function( $post_id, $post ) {
    // 防止递归
    if ( wp_is_post_revision( $post_id ) || defined( 'DOING_SYNC' ) ) {
        return;
    }
    
    define( 'DOING_SYNC', true );
    
    // 1. 同步到远端
    $remote_id = sync_to_remote( $post );
    
    // 2. 立刻回写关联 ID
    update_post_meta( $post_id, 'source_id', $remote_id );
    
    // 3. 校验——这里崩了
    $check = get_post_meta( $post_id, 'source_id', true );
    if ( empty( $check ) ) {
        error_log( '回写失败!remote_id=' . $remote_id ); // 实际日志疯狂输出
    }
}, 10, 2 );

问题出在哪?save_post 触发时,post 数据本身可能还在事务里(尤其外接数据库主从延迟),而 update_post_meta 走的是独立表 wp_postmeta,虽然写进去了,但 get_post_meta 优先读对象缓存——这个缓存键在 save_post 早期就被当前请求占住了,update_post_meta 内部调 wp_cache_delete 清缓存,可如果当前请求里之前有过 get_post_meta 调用(哪怕隐式的),缓存可能以"空数组"形式残留。

✅ 正确写法:绕过缓存层,或者干脆等钩子谢幕

方案一:直接查库验真身(调试/关键校验场景)

add_action( 'save_post', function( $post_id, $post ) {
    if ( wp_is_post_revision( $post_id ) || defined( 'DOING_SYNC' ) ) {
        return;
    }
    define( 'DOING_SYNC', true );
    
    $remote_id = sync_to_remote( $post );
    update_post_meta( $post_id, 'source_id', $remote_id );
    
    // 绕过对象缓存,直接 $wpdb 验货
    global $wpdb;
    $raw = $wpdb->get_var( $wpdb->prepare(
        "SELECT meta_value FROM {$wpdb->postmeta} 
         WHERE post_id = %d AND meta_key = %s",
        $post_id, 'source_id'
    ));
    
    if ( (int) $raw !== $remote_id ) {
        // 真·写入失败,走补偿逻辑
        schedule_sync_retry( $post_id );
    }
}, 10, 2 );

方案二:把"善后"扔到 wp_after_insert_post(WP 5.6+),等 WordPress 自己把事务和缓存收拾干净

// 注册阶段只打标记,不干重活
add_action( 'save_post', function( $post_id ) {
    set_transient( 'pending_sync_' . $post_id, true, MINUTE_IN_SECONDS );
}, 10, 1 );

// 等 WordPress 官宣"insert 彻底完成"
add_action( 'wp_after_insert_post', function( $post_id, $post, $update, $post_before ) {
    if ( ! get_transient( 'pending_sync_' . $post_id ) ) {
        return;
    }
    delete_transient( 'pending_sync_' . $post_id );
    
    $remote_id = sync_to_remote( $post );
    update_post_meta( $post_id, 'source_id', $remote_id );
    
    // 这时候 get_post_meta 稳了,缓存层已重建
    $check = get_post_meta( $post_id, 'source_id', true );
    // ... 正常走
}, 10, 4 );

关键差异点

维度错误写法正确写法
钩子选择<code>save_post</code>(中间态)<code>wp_after_insert_post</code>(终态)
缓存假设默认 <code>update_post_meta</code> 会清干净显式绕过或等终态钩子
事务边界可能踩中未提交数据等 WP 自己收工
递归风险<code>define</code> 防不住多实例并发transient 原子标记更稳

额外踩的坑

1. save_post 在 Quick Edit 批量编辑时也会触发,但 wp_after_insert_post 不会——如果你的同步逻辑依赖完整 post 对象,后者更安全。

2. 如果用了外接对象缓存(Redis/Memcached),wp_cache_delete 在某些客户端版本有"延迟删除"特性,get_post_meta 的返回值可能比 $wpdb 查询旧半秒。

3. wp_after_insert_post 的第四个参数 $post_before 是判断"到底有没有实质变更"的神器,别浪费。

你们有没有在钩子接力赛里被缓存"假阴性"坑过?我这次排查了四个小时,最后发现是 save_postwp_after_insert_post 之间那 50 毫秒的"认知时差"。

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