分析 WordPress `_prime_post_caches()` 函数的源码:如何预加载文章元数据以减少后续查询。

大家好!我是你们今天的WordPress元数据预加载“导游”——老码农。今天咱们不聊风花雪月,就来扒一扒WordPress的“老底”,看看 _prime_post_caches() 这个函数是如何偷偷摸摸地提升网站性能的。准备好,我们要开始“探险”了!

第一站:为什么要预加载元数据?

想象一下,你开了一家小卖部。每次顾客来买东西,你都要重新清点一下库存,确定商品是否还在,价格是多少,生产日期是什么。这效率,估计顾客都要跑光了!

WordPress也是一样。如果每次显示文章,都要重新查询数据库获取文章的各种信息(标题、内容、作者、自定义字段等),那服务器就累死了,网站速度自然也慢成蜗牛。

所以,我们需要一个“进货”的过程,把常用的商品(文章元数据)提前搬到“货架”(缓存)上,顾客来了直接拿,岂不美哉?这就是预加载的意义所在。

第二站:_prime_post_caches() 函数概览

_prime_post_caches() 函数就像一个精明的采购员,它负责从数据库中批量获取文章的各种信息,并把它们放入缓存中,以便后续使用。

这个函数主要做以下几件事情:

  1. 获取文章ID列表: 拿到需要预加载的文章ID。
  2. 预加载文章对象: 将文章的基本信息(标题、内容、状态等)加载到对象缓存中。
  3. 预加载文章术语关系: 将文章与分类、标签等术语的关系加载到缓存中。
  4. 预加载文章元数据(自定义字段): 将文章的自定义字段加载到缓存中。

第三站:代码“解剖”

让我们深入到代码层面,看看 _prime_post_caches() 是如何工作的。

function _prime_post_caches( $post_ids, $lazy_load = false, $update_term_cache = true, $update_meta_cache = true ) {
    global $wpdb;

    if ( empty( $post_ids ) ) {
        return;
    }

    if ( ! is_array( $post_ids ) ) {
        $post_ids = array( $post_ids );
    }

    $post_ids = array_map( 'intval', $post_ids );
    $post_ids = array_unique( $post_ids );

    $non_cached_ids = array();
    foreach ( $post_ids as $id ) {
        if ( ! wp_cache_get( $id, 'posts' ) ) {
            $non_cached_ids[] = $id;
        }
    }

    if ( ! empty( $non_cached_ids ) ) {
        $_posts = $wpdb->get_results( sprintf( "SELECT * FROM $wpdb->posts WHERE ID IN (%s)", implode( ',', $non_cached_ids ) ) );

        if ( $_posts ) {
            update_post_caches( $_posts, false, $lazy_load );
        }
    }

    if ( $update_term_cache ) {
        _prime_post_terms( $post_ids );
    }

    if ( $update_meta_cache ) {
        update_post_meta_cache( $post_ids );
    }
}
  • 参数说明:

    • $post_ids (array|int): 文章ID的数组或单个文章ID。
    • $lazy_load (bool): 是否使用延迟加载。默认为 false。延迟加载主要用于文章内容,可以让文章先显示基本信息,再加载内容。
    • $update_term_cache (bool): 是否更新术语缓存(分类、标签等)。默认为 true
    • $update_meta_cache (bool): 是否更新元数据缓存(自定义字段)。默认为 true
  • 代码逻辑:

    1. 检查输入: 确保 $post_ids 是一个数组,并且所有ID都是整数。去重,避免重复操作。
    2. 检查缓存: 遍历 $post_ids,检查每个ID对应的文章是否已经在缓存中。如果不在缓存中,就将ID添加到 $non_cached_ids 数组中。
    3. 批量查询: 如果 $non_cached_ids 不为空,说明有一些文章不在缓存中,需要从数据库中查询。使用 sprintf()implode() 构造一个SQL查询语句,批量获取这些文章的信息。
    4. 更新文章缓存: 调用 update_post_caches() 函数,将查询到的文章信息放入缓存中。
    5. 更新术语缓存: 如果 $update_term_cachetrue,则调用 _prime_post_terms() 函数,更新文章的术语缓存。
    6. 更新元数据缓存: 如果 $update_meta_cachetrue,则调用 update_post_meta_cache() 函数,更新文章的元数据缓存。

第四站:元数据预加载(update_post_meta_cache()

这才是我们今天的重头戏!update_post_meta_cache() 函数专门负责预加载文章的元数据(自定义字段)。

function update_post_meta_cache( $post_ids ) {
    global $wpdb, $_wp_suspend_cache_addition;

    if ( empty( $post_ids ) ) {
        return;
    }

    if ( ! is_array( $post_ids ) ) {
        $post_ids = array( $post_ids );
    }

    $post_ids = array_map( 'intval', $post_ids );
    $post_ids = array_unique( $post_ids );

    $cache = array();
    $ids = array();
    foreach ( $post_ids as $post_id ) {
        $cached_object = wp_cache_get( $post_id, 'post_meta' );
        if ( false === $cached_object ) {
            $ids[] = $post_id;
        } else {
            $cache[ $post_id ] = $cached_object;
        }
    }

    if ( empty( $ids ) ) {
        return $cache;
    }

    $id_list = implode( ',', $ids );

    $meta_list = $wpdb->get_results(
        "SELECT post_id, meta_key, meta_value
        FROM $wpdb->postmeta
        WHERE post_id IN ($id_list)
        ORDER BY meta_key",
        ARRAY_A
    );

    if ( empty( $meta_list ) ) {
        foreach ( $ids as $id ) {
            wp_cache_add( $id, array(), 'post_meta' );
        }
        return $cache;
    }

    foreach ( $meta_list as $metarow ) {
        $mpid = intval( $metarow['post_id'] );
        $mkey = $metarow['meta_key'];
        $mval = $metarow['meta_value'];

        // Force fields to be array.
        if ( ! isset( $cache[ $mpid ] ) ) {
            $cache[ $mpid ] = array();
        }

        if ( ! isset( $cache[ $mpid ][ $mkey ] ) ) {
            $cache[ $mpid ][ $mkey ] = array( $mval );
        } else {
            $cache[ $mpid ][ $mkey ][] = $mval;
        }
    }

    foreach ( $ids as $id ) {
        if ( ! isset( $cache[ $id ] ) ) {
            $cache[ $id ] = array();
        }
        if ( $_wp_suspend_cache_addition ) {
            wp_cache_set( $id, $cache[ $id ], 'post_meta' );
        } else {
            wp_cache_add( $id, $cache[ $id ], 'post_meta' );
        }
    }

    return $cache;
}
  • 参数说明:

    • $post_ids (array|int): 文章ID的数组或单个文章ID。
  • 代码逻辑:

    1. 检查输入: 确保 $post_ids 是一个数组,并且所有ID都是整数。去重,避免重复操作。
    2. 检查缓存: 遍历 $post_ids,检查每个ID对应的元数据是否已经在缓存中。如果不在缓存中,就将ID添加到 $ids 数组中。
    3. 批量查询: 如果 $ids 不为空,说明有一些文章的元数据不在缓存中,需要从数据库中查询。使用 sprintf()implode() 构造一个SQL查询语句,批量获取这些文章的元数据。
    4. 整理数据: 遍历查询到的元数据,将它们按照文章ID和元数据键名组织成一个多维数组 $cache。例如:

      $cache = array(
          123 => array(
              'color' => array('red'),
              'price' => array('19.99'),
          ),
          456 => array(
              'size' => array('large', 'medium'),
          ),
      );

      这里,文章ID为123的文章有一个名为 color 的元数据,值为 red;还有一个名为 price 的元数据,值为 19.99。文章ID为456的文章有一个名为 size 的元数据,值为 largemedium。 注意,元数据的值始终是一个数组,即使只有一个值。

    5. 更新元数据缓存: 遍历 $ids,将每个文章的元数据放入缓存中。使用 wp_cache_add() 函数将元数据添加到缓存中。如果缓存已经存在,则使用 wp_cache_set() 函数更新缓存。
    6. 返回缓存: 返回包含所有文章元数据的 $cache 数组。

第五站:举个栗子

假设我们有两篇文章,ID分别为1和2。它们的元数据如下:

  • 文章ID 1:
    • _yoast_wpseo_title = "Hello World! | My Blog"
    • _yoast_wpseo_metadesc = "This is my first post."
  • 文章ID 2:
    • _thumbnail_id = "123"
    • custom_field = "Some custom value"

当我们调用 update_post_meta_cache( array( 1, 2 ) ) 时,update_post_meta_cache() 函数会执行以下步骤:

  1. 检查缓存: 发现文章1和2的元数据都不在缓存中。
  2. 批量查询: 执行以下SQL查询:

    SELECT post_id, meta_key, meta_value
    FROM wp_postmeta
    WHERE post_id IN (1, 2)
    ORDER BY meta_key
  3. 整理数据: 将查询结果整理成以下数组:

    $cache = array(
        1 => array(
            '_yoast_wpseo_metadesc' => array( 'This is my first post.' ),
            '_yoast_wpseo_title' => array( 'Hello World! | My Blog' ),
        ),
        2 => array(
            '_thumbnail_id' => array( '123' ),
            'custom_field' => array( 'Some custom value' ),
        ),
    );
  4. 更新元数据缓存:$cache 数组中的元数据放入缓存中。

现在,当我们调用 get_post_meta( 1, '_yoast_wpseo_title', true ) 时,WordPress可以直接从缓存中获取元数据,而不需要再次查询数据库,速度大大提升!

第六站:_prime_post_terms() 函数 (小插曲)

虽然今天的主题是元数据,但 _prime_post_terms() 函数也功不可没,它负责预加载文章的术语(分类、标签等)。 它的作用是提前将文章与分类和标签的关系加载到缓存中,避免重复查询数据库。 这样,当我们需要显示文章的分类或标签时,就可以直接从缓存中获取,提高网站的性能。

function _prime_post_terms( $post_ids, $taxonomy = 'all', $update_term_cache = true ) {
    global $wpdb;

    if ( empty( $post_ids ) ) {
        return;
    }

    if ( ! is_array( $post_ids ) ) {
        $post_ids = array( $post_ids );
    }

    $post_ids = array_map( 'intval', $post_ids );
    $post_ids = array_unique( $post_ids );

    $taxonomies = get_taxonomies( array( 'update_count_callback' => '_update_post_term_count' ) );

    if ( 'all' !== $taxonomy ) {
        $taxonomies = array_intersect( (array) $taxonomy, $taxonomies );
    }

    $cache = array();
    $non_cached_ids = array();

    foreach ( $post_ids as $id ) {
        if ( ! $terms = get_object_term_cache( $id, $taxonomies ) ) {
            $non_cached_ids[] = $id;
        } else {
            $cache[ $id ] = $terms;
        }
    }

    if ( empty( $non_cached_ids ) ) {
        return $cache;
    }

    $terms = wp_get_object_terms( $non_cached_ids, $taxonomies, array( 'update_term_cache' => $update_term_cache ) );

    return $terms;
}

第七站:总结与最佳实践

_prime_post_caches() 函数及其子函数 update_post_meta_cache()_prime_post_terms() 是WordPress性能优化的重要组成部分。它们通过预加载文章的各种信息,减少了数据库查询次数,提高了网站的响应速度。

  • 何时使用 _prime_post_caches()

    • 在循环显示文章列表之前。
    • 在显示单个文章之前。
    • 在任何需要频繁访问文章信息的场景。
  • 最佳实践:

    • 尽量批量预加载文章信息,避免多次调用 _prime_post_caches()
    • 根据实际需求选择性地更新术语缓存和元数据缓存。如果不需要显示分类和标签,可以设置 $update_term_cachefalse。如果不需要显示自定义字段,可以设置 $update_meta_cachefalse
    • 使用缓存插件,例如WP Super Cache、W3 Total Cache等,进一步提升网站性能。这些插件可以缓存整个页面,避免每次都执行PHP代码和查询数据库。
    • 定期清理数据库,删除不必要的文章和元数据,减少数据库的大小。

第八站:一点“题外话”

预加载虽然好,但也要适度。预加载太多的数据会占用服务器的内存,反而会降低性能。所以,要根据实际情况进行调整,找到一个平衡点。

好了,今天的“探险”就到这里。希望大家对WordPress的元数据预加载有了更深入的了解。记住,优化网站性能就像“挤牙膏”,一点一滴的积累才能看到效果! 各位码农,下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注