详解 WordPress `get_transient()` 函数源码:键值对缓存与过期时间的实现原理。

各位观众老爷们,大家好!今天咱们来聊聊WordPress里一个很实用,但又容易被忽略的小家伙:get_transient()。 别看它名字平平无奇,在优化网站性能方面,它可是个隐藏的大佬。 今天我就扒开它的源码,让大家彻底明白它到底是怎么玩的,怎么用它来让你的网站飞起来。

开场白:为啥需要缓存?

想象一下,每次有人访问你的网站,服务器都要辛辛苦苦地从数据库里捞数据,然后拼装成网页,是不是太累了? 如果访问量一大,服务器就容易卡壳,用户体验直线下降。 这时候,缓存就闪亮登场了。

缓存就像是你家里的冰箱,把常用的东西(比如数据库查询结果)提前放进去,下次要用的时候,直接从冰箱里拿,省时省力。 WordPress里的Transient API就是用来干这个的,而get_transient()就是从这个“冰箱”里拿东西的钥匙。

get_transient():拿东西的钥匙

get_transient()函数的作用很简单,就是根据你给的键名(key),从Transient缓存里取出对应的值。 如果缓存里没有这个键,或者缓存已经过期了,它就返回 false

<?php
/**
 * Retrieve the value of a transient.
 *
 * @since 2.8.0
 *
 * @param string $transient Transient name. Expected to not be SQL-escaped.
 * @return mixed Value of transient. If does not exist, returns false.
 */
function get_transient( string $transient ) {
    return get_site_transient( $transient );
}

可以看到,get_transient() 本身只是一个简单的壳,它直接调用了 get_site_transient()。 所以,要深入理解,我们得看看 get_site_transient() 到底做了什么。

get_site_transient():深入虎穴

get_site_transient() 函数才是真正干活的家伙。 它负责从数据库里读取Transient数据,并检查是否过期。

<?php
/**
 * Get the value of a site transient.
 *
 * @since 2.8.0
 *
 * @param string $transient Transient name. Expected to not be SQL-escaped.
 * @return mixed Value of transient. If does not exist, returns false.
 */
function get_site_transient( string $transient ) {
    global $wpdb;

    $transient = sanitize_key( $transient );
    $transient_option = '_site_transient_' . $transient;

    /**
     * Filters the value of an existing site transient.
     *
     * The dynamic portion of the hook name, `$transient`, refers to the transient name.
     *
     * Passing a non-null value to the filter will effectively short-circuit get_site_transient(),
     * returning the passed value instead.
     *
     * @since 2.9.0
     *
     * @param mixed  $pre_transient The default value to return if the transient does not exist.
     *                              Any value other than null will short-circuit the retrieval
     *                              and return the passed value.
     * @param string $transient     Transient name.
     */
    $pre = apply_filters( "pre_site_transient_{$transient}", null, $transient );
    if ( null !== $pre ) {
        return $pre;
    }

    $value = get_option( $transient_option );

    if ( false === $value ) {
        return false;
    }

    $expiration = get_option( '_site_transient_timeout_' . $transient );

    if ( false !== $expiration && time() > $expiration ) {

        delete_site_transient( $transient );
        return false;
    }

    /**
     * Filters an existing site transient's value.
     *
     * The dynamic portion of the hook name, `$transient`, refers to the transient name.
     *
     * @since 2.9.0
     *
     * @param mixed  $value     Value of transient.
     * @param string $transient Transient name.
     */
    return apply_filters( "site_transient_{$transient}", $value, $transient );
}

让我们一步步拆解:

  1. 安全第一:sanitize_key()

    $transient = sanitize_key( $transient );

    首先,它会对你传入的键名($transient)进行安全处理,使用 sanitize_key() 函数,确保键名是合法的,防止SQL注入之类的安全问题。 sanitize_key()函数会将键名转换为小写,并移除所有非字母、数字、下划线、短横线和点号的字符。

  2. 构建Option名:_site_transient_ 前缀

    $transient_option = '_site_transient_' . $transient;

    接下来,它会在你的键名前面加上 _site_transient_ 前缀,生成一个Option名($transient_option)。 这个Option名就是用来在数据库里存储实际Transient数据的地方。 例如,如果你传入的键名是 my_data,那么生成的Option名就是 _site_transient_my_data

  3. 过滤器:pre_site_transient_{$transient}

    $pre = apply_filters( "pre_site_transient_{$transient}", null, $transient );
    if ( null !== $pre ) {
        return $pre;
    }

    这里用到了一个过滤器 pre_site_transient_{$transient}。 这个过滤器允许你提前修改或者直接返回Transient的值,而不用去数据库里查。 如果你给这个过滤器设置了一个非 null 的返回值,那么get_site_transient()函数会直接返回你设置的值,不再继续执行后面的代码。 这在某些特殊情况下非常有用,比如你想手动控制Transient的返回值。

  4. 读取Option:get_option()

    $value = get_option( $transient_option );
    
    if ( false === $value ) {
        return false;
    }

    现在,它终于要从数据库里读取数据了。 它使用 get_option() 函数,根据之前生成的Option名($transient_option),从 wp_options 表里读取对应的Option值。 如果数据库里没有这个Option,get_option() 会返回 falseget_site_transient() 也会跟着返回 false,表示缓存未命中。

  5. 检查过期时间:_site_transient_timeout_ 前缀

    $expiration = get_option( '_site_transient_timeout_' . $transient );
    
    if ( false !== $expiration && time() > $expiration ) {
    
        delete_site_transient( $transient );
        return false;
    }

    重点来了! 这里要检查缓存是否过期。 它会先根据键名,加上 _site_transient_timeout_ 前缀,生成一个Timeout Option名,然后使用 get_option() 函数读取对应的过期时间。 例如,如果你的键名是 my_data,那么生成的Timeout Option名就是 _site_transient_timeout_my_data

    如果找到了过期时间,并且当前时间已经超过了过期时间,那么它会调用 delete_site_transient() 函数删除这个Transient,并返回 false,表示缓存已过期。

  6. 过滤器:site_transient_{$transient}

    return apply_filters( "site_transient_{$transient}", $value, $transient );

    最后,它又使用了一个过滤器 site_transient_{$transient}。 这个过滤器允许你在返回Transient值之前,对它进行最后的修改。 你可以通过这个过滤器,对Transient值进行一些处理,然后再返回给调用者。

set_transient()delete_transient():设置和删除

光会拿东西还不行,还得会放东西和扔东西。 set_transient()delete_transient() 函数就是用来干这个的。

  • set_transient():往冰箱里放东西

    set_transient() 函数用来设置一个Transient缓存。 它接受三个参数:键名($transient)、值($value)和过期时间($expiration)。

    <?php
    /**
     * Set/update the value of a transient.
     *
     * You must use underscores for the name of the transient.
     *
     * @since 2.8.0
     *
     * @param string $transient  Transient name. Expected to not be SQL-escaped. Must be
     *                           40 characters or less.
     * @param mixed  $value      Transient value. Must be serializable if non-scalar.
     *                           Expected to not be SQL-escaped.
     * @param int    $expiration Optional. Time until expiration in seconds. Default 0 (no expiration).
     * @return bool False if value was not set and true if value was set.
     */
    function set_transient( string $transient, mixed $value, int $expiration = 0 ) {
        return set_site_transient( $transient, $value, $expiration );
    }

    同样,set_transient() 也是一个壳,它调用了 set_site_transient()

  • delete_transient():把冰箱里的东西扔掉

    delete_transient() 函数用来删除一个Transient缓存。 它接受一个参数:键名($transient)。

    <?php
    /**
     * Delete a transient.
     *
     * @since 2.8.0
     *
     * @param string $transient Transient name. Expected to not be SQL-escaped.
     * @return bool True if the transient was successfully deleted, false otherwise.
     */
    function delete_transient( string $transient ) {
        return delete_site_transient( $transient );
    }

    delete_transient() 也是一个壳,它调用了 delete_site_transient()

set_site_transient()delete_site_transient():幕后英雄

这两个函数才是真正负责设置和删除Transient缓存的。 它们的实现原理和 get_site_transient() 类似,也是通过操作 wp_options 表来实现的。

  • set_site_transient():设置Transient

    <?php
    /**
     * Set the value of a site transient.
     *
     * @since 2.8.0
     *
     * @param string $transient  Transient name. Expected to not be SQL-escaped. Must be
     *                           40 characters or less.
     * @param mixed  $value      Transient value. Must be serializable if non-scalar.
     *                           Expected to not be SQL-escaped.
     * @param int    $expiration Optional. Time until expiration in seconds. Default 0 (no expiration).
     * @return bool False if value was not set and true if value was set.
     */
    function set_site_transient( string $transient, mixed $value, int $expiration = 0 ) {
        global $wpdb;
    
        $transient = sanitize_key( $transient );
    
        /**
         * Filters the value of an existing site transient before it is set.
         *
         * The dynamic portion of the hook name, `$transient`, refers to the transient name.
         *
         * @since 2.9.0
         *
         * @param mixed  $value     New value of transient.
         * @param string $transient Transient name.
         * @param int    $expiration Time until expiration in seconds.
         */
        $value = apply_filters( "pre_set_site_transient_{$transient}", $value, $transient, $expiration );
    
        if ( is_serialized( $value ) ) {
            $value = wp_unslash( $value );
        }
    
        $transient_timeout = '_site_transient_timeout_' . $transient;
        $transient = '_site_transient_' . $transient;
    
        if ( false === get_option( $transient ) ) {
    
            $autoload = 'yes';
            if ( $expiration ) {
                $autoload = 'no';
                add_option( $transient_timeout, time() + $expiration, '', $autoload );
            }
    
            $result = add_option( $transient, $value, '', $autoload );
    
            if ( $result ) {
    
                /**
                 * Fires after the value of a site transient has been set.
                 *
                 * The dynamic portion of the hook name, `$transient`, refers to the transient name.
                 *
                 * @since 2.9.0
                 *
                 * @param mixed  $value     Transient value.
                 * @param string $transient Transient name.
                 * @param int    $expiration Time until expiration in seconds.
                 */
                do_action( "set_site_transient_{$transient}", $value, $transient, $expiration );
    
                return true;
            }
    
            return false;
        } else {
    
            if ( $expiration ) {
                update_option( $transient_timeout, time() + $expiration );
            } else {
                delete_option( $transient_timeout );
            }
    
            $result = update_option( $transient, $value );
    
            if ( $result ) {
    
                /**
                 * Fires after the value of an existing site transient has been updated.
                 *
                 * The dynamic portion of the hook name, `$transient`, refers to the transient name.
                 *
                 * @since 2.9.0
                 *
                 * @param mixed  $value     Transient value.
                 * @param string $transient Transient name.
                 * @param int    $expiration Time until expiration in seconds.
                 */
                do_action( "set_site_transient_{$transient}", $value, $transient, $expiration );
    
                return true;
            }
    
            return false;
        }
    }
    • 安全处理:sanitize_key()
      首先,会对键名进行安全处理,防止SQL注入。
    • 过滤器:pre_set_site_transient_{$transient}
      允许在设置Transient之前,修改Transient的值。
    • 处理序列化数据
      如果是序列化数据,会进行反斜杠处理。
    • 构建Option名:_site_transient_timeout__site_transient_
      分别构建存储过期时间和Transient值的Option名。
    • 判断Option是否存在
      如果Option不存在,则使用 add_option() 添加Option。 如果Option存在,则使用 update_option() 更新Option。
    • 设置过期时间
      如果设置了过期时间,则将过期时间存储在 _site_transient_timeout_ Option中。
    • 过滤器:set_site_transient_{$transient}
      在设置Transient之后,触发一个Action,允许执行一些额外的操作。
  • delete_site_transient():删除Transient

    <?php
    /**
     * Delete a site transient.
     *
     * @since 2.8.0
     *
     * @param string $transient Transient name. Expected to not be SQL-escaped.
     * @return bool True if the transient was successfully deleted, false otherwise.
     */
    function delete_site_transient( string $transient ) {
    
        $transient = sanitize_key( $transient );
    
        /**
         * Fires before a site transient is deleted.
         *
         * The dynamic portion of the hook name, `$transient`, refers to the transient name.
         *
         * @since 2.9.0
         *
         * @param string $transient Transient name.
         */
        do_action( "delete_site_transient_{$transient}", $transient );
    
        $option_timeout = '_site_transient_timeout_' . $transient;
        $option = '_site_transient_' . $transient;
    
        $result = delete_option( $option );
    
        if ( $result ) {
            delete_option( $option_timeout );
    
            /**
             * Fires after a site transient is deleted.
             *
             * The dynamic portion of the hook name, `$transient`, refers to the transient name.
             *
             * @since 2.9.0
             *
             * @param string $transient Transient name.
             */
            do_action( "deleted_site_transient_{$transient}", $transient );
    
            return true;
        }
    
        return false;
    }
    • 安全处理:sanitize_key()
      首先,会对键名进行安全处理,防止SQL注入。
    • 过滤器:delete_site_transient_{$transient}
      在删除Transient之前,触发一个Action,允许执行一些额外的操作。
    • 构建Option名:_site_transient_timeout__site_transient_
      分别构建存储过期时间和Transient值的Option名。
    • 删除Option:delete_option()
      分别删除存储过期时间和Transient值的Option。
    • 过滤器:deleted_site_transient_{$transient}
      在删除Transient之后,触发一个Action,允许执行一些额外的操作。

Transient的存储方式

Transient数据实际上是存储在 wp_options 表里的。 每个Transient都对应两条记录:

  • 一条记录存储Transient的值,Option名以 _site_transient_ 开头。
  • 一条记录存储Transient的过期时间,Option名以 _site_transient_timeout_ 开头。
Option Name Option Value
_site_transient_my_data a:1:{s:5:"value";i:123;} (序列化的数据)
_site_transient_timeout_my_data 1678886400 (Unix时间戳)

使用场景举例

假设你要缓存一个需要耗时计算的结果:

<?php
$data = get_transient( 'my_complex_data' );

if ( false === $data ) {
    // 缓存未命中,需要重新计算
    $data = calculate_complex_data(); // 假设这个函数很耗时

    // 将结果缓存起来,有效期1小时
    set_transient( 'my_complex_data', $data, 3600 );
}

// 使用缓存的数据
echo $data;

注意事项

  • 键名长度限制: Transient的键名长度不能超过40个字符。
  • 数据类型: Transient可以存储各种类型的数据,包括字符串、数字、数组、对象等。 但是,非标量数据需要序列化才能存储。
  • 过期时间: 合理设置过期时间非常重要。 如果过期时间太短,缓存就起不到作用。 如果过期时间太长,缓存的数据可能就过时了。
  • Autoload: Transient的Autoload选项默认是 yes,表示在WordPress加载时自动加载。 如果你的Transient数据量很大,建议将Autoload设置为 no,以减少内存消耗。

总结

get_transient() 函数是 WordPress Transient API 的核心函数之一,它通过操作 wp_options 表,实现了键值对缓存和过期时间的功能。 合理使用Transient API,可以有效提高网站的性能和用户体验。

希望今天的讲座对大家有所帮助! 以后再遇到性能问题,记得想起 get_transient() 这个小可爱,它能让你的网站跑得更快,飞得更高!

发表回复

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