剖析 WordPress `get_option()` 函数源码:它如何通过 `wp_options` 表获取数据,并利用对象缓存进行优化。

各位观众老爷们,晚上好!我是老码农,今天咱们来聊聊 WordPress 里的一个老朋友,get_option() 函数。这货看起来平平无奇,但却是 WordPress 性能优化的一大利器,它背后默默地操纵着 wp_options 表,并巧妙地运用了对象缓存。今天,咱们就扒开它的底裤,看看它到底是怎么运作的。

一、get_option() 函数:初识庐山真面目

首先,咱们得知道 get_option() 是干嘛的。简单来说,它就是用来从 WordPress 的选项数据库中获取指定选项的值的。这些选项可以包括网站标题、描述、主题设置等等。

函数的基本语法如下:

<?php
/**
 * Retrieves an option value based on an option name.
 *
 * If the option does not exist or does not have a value, then the return will be false.
 * This is preferable to employing `isset( get_option( 'nonexistent_option' ) )`,
 * as that will call `get_option()` regardless of whether the option is set.
 *
 * @since 1.5.0
 * @since 5.5.0 Added the `$default` parameter.
 *
 * @param string $option  Name of option to retrieve. Expected to not be SQL-escaped.
 * @param mixed  $default Optional. Default value to return if the option does not exist.
 *                        Default: false.
 * @return mixed Value set for the option.
 */
function get_option( $option, $default = false ) {
    global $wpdb, $wp_suspend_cache_addition;

    $option = trim( $option );
    if ( empty( $option ) ) {
        return false;
    }

    // Prevent non-existent options from triggering multiple queries.
    $notoptions = wp_cache_get( 'notoptions', 'options' );

    if ( isset( $notoptions[ $option ] ) ) {
        return $default;
    }

    $alloptions = wp_load_alloptions();

    if ( isset( $alloptions[ $option ] ) ) {
        return $alloptions[ $option ];
    }

    $cache_value = wp_cache_get( $option, 'options' );

    if ( false !== $cache_value ) {
        /**
         * Filters the value of an existing option before it is retrieved from the cache.
         *
         * The dynamic portion of the hook name, `$option`, refers to the option name.
         *
         * @since 2.5.0
         *
         * @param mixed $value The value of the option.
         */
        return apply_filters( "pre_option_{$option}", $cache_value );
    }

    $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) );

    // Has to be get_row rather than get_var because of funkiness with database object caching.
    if ( is_object( $row ) ) {
        $value = $row->option_value;

        // Maybe unserialize.
        if ( 'yes' === $wp_suspend_cache_addition ) {
            $value = maybe_unserialize( $value );
        } else {
            $value = maybe_unserialize( $value );
            wp_cache_add( $option, $value, 'options' );
        }

        /** This filter is documented in wp-includes/option.php */
        return apply_filters( "pre_option_{$option}", $value );
    }

    // If option is not in alloptions cache.
    if ( ! isset( $notoptions[ $option ] ) ) {
        $notoptions[ $option ] = true;
        wp_cache_set( 'notoptions', $notoptions, 'options' );
    }

    return $default;
}

参数说明:

  • $option:要获取的选项名,字符串类型。
  • $default:可选参数,如果选项不存在,则返回的默认值。默认为 false

返回值:

  • 如果选项存在,则返回选项的值。
  • 如果选项不存在,则返回 $default 参数指定的值,默认为 false

二、wp_options 表:选项的大本营

WordPress 的选项数据都存储在 wp_options 表中。这个表结构简单粗暴,主要有以下几个字段:

字段名 数据类型 说明
option_id BIGINT(20) 主键,自增 ID。
option_name VARCHAR(191) 选项名,唯一索引。
option_value LONGTEXT 选项值,可以存储任何类型的数据,包括字符串、数字、数组、对象等等。WordPress 会自动进行序列化和反序列化。
autoload VARCHAR(20) 是否自动加载。如果设置为 yes,则该选项会在 WordPress 初始化时自动加载到内存中,以便快速访问。如果设置为 no,则只有在调用 get_option() 时才会从数据库中加载。这个字段对于性能至关重要,后面会详细讲。

三、get_option() 的内部运作:抽丝剥茧

现在,咱们深入到 get_option() 的源码中,看看它到底是怎么工作的。

  1. 参数校验与空选项处理:
    $option = trim( $option );
    if ( empty( $option ) ) {
        return false;
    }

首先,函数会对传入的选项名进行去空格处理,如果选项名为空,则直接返回 false。这属于基本的参数校验。

  1. notoptions 缓存:避免无效查询
    $notoptions = wp_cache_get( 'notoptions', 'options' );

    if ( isset( $notoptions[ $option ] ) ) {
        return $default;
    }

这部分代码非常关键。它首先尝试从对象缓存中获取 notoptions 缓存组。notoptions 是一个数组,记录了哪些选项在数据库中不存在。如果我们要获取的选项名存在于 notoptions 中,说明这个选项之前已经被查询过,并且确认不存在,那么函数会直接返回 $default 值,避免重复查询数据库。

这个机制非常聪明,可以有效地减少无效的数据库查询,尤其是在一个页面中多次尝试获取不存在的选项时。

  1. alloptions 缓存:快速访问 autoload 选项
    $alloptions = wp_load_alloptions();

    if ( isset( $alloptions[ $option ] ) ) {
        return $alloptions[ $option ];
    }

wp_load_alloptions() 函数会从数据库中加载所有 autoload 设置为 yes 的选项,并将它们存储在一个数组中。这个数组会被缓存起来,以便快速访问。

如果我们要获取的选项名存在于 alloptions 数组中,说明这个选项是 autoload 的,并且已经被加载到内存中,那么函数会直接返回选项的值,而无需查询数据库。

autoload 选项的存在,就是为了优化性能。对于一些常用的、需要在每个页面都访问的选项,应该将其 autoload 设置为 yes,以便快速访问。但是,autoload 选项的数量不宜过多,否则会增加内存占用,反而会降低性能。

  1. 选项缓存:从对象缓存中直接获取
    $cache_value = wp_cache_get( $option, 'options' );

    if ( false !== $cache_value ) {
        return apply_filters( "pre_option_{$option}", $cache_value );
    }

如果选项既不在 notoptions 中,也不在 alloptions 中,那么函数会尝试从对象缓存中获取该选项的值。如果缓存中存在该选项的值,则直接返回,并应用 pre_option_{$option} 过滤器。

WordPress 的对象缓存机制非常强大,可以将数据库查询结果缓存到内存中,以便快速访问。get_option() 函数充分利用了对象缓存,可以有效地减少数据库查询次数,提高网站性能。

  1. 数据库查询:最后的手段
    $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) );

    if ( is_object( $row ) ) {
        $value = $row->option_value;

        $value = maybe_unserialize( $value );
        wp_cache_add( $option, $value, 'options' );

        return apply_filters( "pre_option_{$option}", $value );
    }

如果以上所有方法都失败了,那么 get_option() 函数会执行最后的手段:查询数据库。它会使用 WPDB 类来执行 SQL 查询,从 wp_options 表中获取指定选项的值。

如果查询成功,函数会将选项值进行反序列化(如果需要),然后将其添加到对象缓存中,以便下次快速访问。最后,函数返回选项值,并应用 pre_option_{$option} 过滤器。

  1. 更新 notoptions 缓存:记录不存在的选项
    if ( ! isset( $notoptions[ $option ] ) ) {
        $notoptions[ $option ] = true;
        wp_cache_set( 'notoptions', $notoptions, 'options' );
    }

    return $default;

如果数据库查询失败,说明该选项在数据库中不存在。为了避免下次重复查询,函数会将该选项名添加到 notoptions 缓存中。

四、对象缓存:性能优化的基石

get_option() 函数的性能优化,很大程度上依赖于 WordPress 的对象缓存机制。对象缓存可以将数据库查询结果缓存到内存中,以便快速访问,从而减少数据库查询次数,提高网站性能。

WordPress 提供了多种对象缓存的实现方式,包括:

  • Transient API: 简单易用,适用于存储临时数据。
  • WP_Object_Cache 类: 提供更高级的缓存控制,适用于存储复杂数据。
  • 外部缓存: 使用 Memcached、Redis 等外部缓存系统,可以提供更高的性能和可扩展性。

get_option() 函数主要使用 WP_Object_Cache 类来进行对象缓存。它会将选项值存储在 options 缓存组中,并使用选项名作为缓存键。

五、autoload 选项:权衡利弊

autoload 选项是一个双刃剑。它可以提高常用选项的访问速度,但也会增加内存占用。

  • 优点: 减少数据库查询次数,提高网站性能。
  • 缺点: 增加内存占用,可能导致性能下降。

因此,在使用 autoload 选项时,需要权衡利弊,只将常用的、需要在每个页面都访问的选项设置为 yes

六、pre_option_{$option} 过滤器:灵活扩展

get_option() 函数在返回选项值之前,会应用 pre_option_{$option} 过滤器。这个过滤器允许开发者修改选项值,或者从其他来源获取选项值。

例如,我们可以使用 pre_option_{$option} 过滤器来实现多站点共享选项:

<?php
add_filter( 'pre_option_my_option', 'my_get_shared_option' );

function my_get_shared_option( $value ) {
    if ( is_multisite() && ! get_current_blog_id() ) {
        switch_to_blog( 1 ); // Switch to the main site.
        $value = get_option( 'my_option' );
        restore_current_blog(); // Switch back to the current site.
    }

    return $value;
}

这段代码会在多站点环境下,如果当前站点不是主站点,则从主站点获取 my_option 选项的值。

七、总结:get_option() 的智慧

get_option() 函数看似简单,但却蕴含着 WordPress 性能优化的智慧。它通过以下手段来提高性能:

  • 参数校验: 避免无效操作。
  • notoptions 缓存: 避免无效查询。
  • alloptions 缓存: 快速访问 autoload 选项。
  • 对象缓存: 减少数据库查询次数。
  • autoload 选项: 权衡利弊。
  • pre_option_{$option} 过滤器: 灵活扩展。

掌握 get_option() 函数的运作机制,可以帮助我们更好地理解 WordPress 的性能优化策略,并编写更高效的代码。

希望今天的讲座对大家有所帮助。下次再见!

发表回复

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