WordPress 7.0 WP-Cron Transients Object Cache Pro 后台任务 定时任务 缓存层
WordPress 7.0(2026-05-20 发布)把后台任务层(WP-Cron)和瞬态缓存层(Transients)都做了一轮核心重写:默认 wp-cron.php 改为单飞锁(single-flight lock),避免了并发触发把任务跑 2 次的老坑;set_transient() 默认走 Object Cache 之后,第一次未命中会主动回填 wp_options,避免了之前把 60 秒高频缓存直接灌进数据库。现在生产站点在这两层上踩到的坑,跟 6.x 不是同一组问题——旧的"DISABLE_WP_CRON 之后忘了写系统 crontab"依然常见,但新出现的"7.0.1 之前 Object Cache Pro 与单飞锁冲突导致定时发布卡死 30 秒"才是 2026 年最值得写的。
这篇能帮你解决什么
- WP-Cron 任务被并发触发 2 次(订单通知/会员到期发 2 遍)怎么定位到锁文件残留
- Transients 在 Redis Object Cache Pro 上跑得很好,但只要 wp_options autoload 体积一膨胀就突然全打 0 缓存
- 7.0 默认 `wp-cron.php` 触发频率被压到 60 秒,旧的 `*/5 * * * * wp cron event run` 系统 cron 节奏应该改成多少
- DISABLE_WP_CRON + 系统 crontab 在 7.0 上如何正确组合,否则就是定时发布永远不触发
- Object Cache Pro 与 cron 锁的 race condition(Trac #62757,7.0.1 修复)
📌 五个坑快速索引
1. 坑 1:默认 WP-Cron 触发 2 次的"看起来是 bug 其实不是 bug"——HTTP 边界竞态 + 7.0 之前没有单飞锁
2. 坑 2:DISABLE_WP_CRON 后忘记部署系统 crontab,定时发布永远 stuck in "Scheduled"
3. 坑 3:Transients 直接打 wp_options,把 4MB autoload 撑成 28MB
4. 坑 4:Object Cache Pro 与 7.0 单飞锁 race condition(Trac #62757)——7.0.1 之前定时发布卡 30 秒
5. 坑 5:Redis 满后 Transients 全部回落到 wp_options,命中率从 95% 掉到 0% 没有任何告警
🛠️ 前置确认
wp --version # WP-CLI 2.11+(7.0 兼容版本)
wp core version # WordPress 7.0 或 7.0.1(推荐 7.0.1,2026-07-09 修了 Trac #62757)
wp option get blog_charset
php -v # 8.2+
wp eval 'echo wp_get_environment_type().PHP_EOL;'
# 应输出 production(开发机是 development)
接下来 5 个坑从最常见的"任务不执行"开始讲。
💣 坑 1:默认 WP-Cron 触发 2 次——HTTP 边界竞态
WordPress 7.0 之前的 WP-Cron 是典型的"无锁设计":每次前端页面加载时,wp-cron.php 检查 wp_options.cron 里的下一个到期 timestamp,如果过期就 spawn 一次 wp_remote_post 到自己(/wp-cron.php?doing_wp_cron=),这个 spawn 是非阻塞的 HTTP 请求,PHP 立刻返回页面给用户。问题在于:两个并发用户同时触发,就会有两个 spawn 一起到同一个 hook。表现:
# 现象:会员到期的邮件,一天内发 2 次
select count(*) from wp_options where option_name = 'cron';
-- wp_options cron 字段是序列化的数组,记录每个 hook 的下次执行时间
# 你会看到同一时间戳出现 2 次 spawn 记录(从 wp_actions / debug.log 看)
WordPress 7.0(2026-05-20)在这块加了单飞锁,函数 wp_run_scheduler() 里引入了 WP_CRON_LOCK_TIMEOUT 常量(默认 60 秒),同一时间只允许一个 cron spawn 真正执行后续 hook。其他并发请求会拿不到锁直接退出。验证方法:
# 在 wp-config.php 里临时打开 cron 调试
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_CRON_LOCK_TIMEOUT', 60);
define('ALTERNATE_WP_CRON', false);
# 加载任意页面后看 wp-content/debug.log:
tail -f wp-content/debug.log | grep -E "cron|spawn"
# 应看到:
# Cron spawn started: 1234567890.123
# Cron spawn locked, exiting
修复:7.0+ 用户不用改代码,旧站升级到 7.0 默认就修好了。还在 6.9 LTS 或者更老的站,建议加这两个保护:
// wp-content/mu-plugins/cron-singleflight.php
add_filter('pre_schedule_event', function($null, $event) {
// 用 wp_options 第二个 transient 字段做 ad-hoc 锁
if (get_transient('cron_lock_' . $event->hook)) {
return $null;
}
set_transient('cron_lock_' . $event->hook, 1, 30);
return $event;
}, 10, 2);
⚠️ 坑提醒:用 transient 做锁时一定要设过期时间(30 秒),否则站点故障恢复时所有任务都被卡死。这个补丁我自己用到 2026 Q1 才删,转用 7.0 默认锁。
💣 坑 2:DISABLE_WP_CRON 后忘了部署系统 crontab——定时发布永远 stuck
很多人在 wp-config.php 里加了:
define('DISABLE_WP_CRON', true);
来自 6/29 我自己写的那篇 wp-config.php 优化 8 项实战 里提到的"P95 TTFB -43%"。但**很多人加完这一行就完事了,没有配系统 crontab**:
# 错误示范(你以为这样写了)
# wp-config.php
define('DISABLE_WP_CRON', true);
# 但系统 crontab 里完全没有 wp cron event run --due-now
# 现象:wp-admin 里 "Scheduled" 状态的 post 永远不发布
# select option_value from wp_options where option_name = 'cron' limit 1;
# 你会看到 post_publish 任务的 timestamp 早过了今天,但 wp_posts 里 post_status 还是 future
生产站点的标准做法是 DISABLE_WP_CRON + 系统 crontab 每分钟一次:
# /etc/cron.d/wordpress-cron
* * * * * www-data cd /var/www/html && /usr/local/bin/wp cron event run --due-now --quiet >> /var/log/wp-cron.log 2>&1
⚠️ 关键细节:
- 7.0 默认 `wp-cron.php` 触发频率从原来的 30 秒变到 60 秒(Trac #62384,7.0.1 调整为 60 秒明确值)。所以如果你还有遗留的 `*/2 * * * *`(即每 2 分钟),最低频率要求改成 **`* * * * *`(每分钟)** 即可满足 7.0 的预期最小响应粒度
- `--quiet` 标志必须加,否则每天 1440 次 cron run 全部写进 log,30 天就 43,200 行 noise
- 用 `wp cron event run --due-now` 比 `wp cron event run --all` 安全:只跑过期的,不会重复执行未到期任务
- 容器化部署(Docker)里,WP-CLI 命令必须在 entrypoint 之外跑,否则每次 container restart 会重置掉 cron 锁状态
验证:
# 在 wp-admin 里 schedule 一个 10 分钟后的 post,然后到时间后查
wp post list --post_status=future --post_type=post --format=table
# 10 分钟后应该自动变为 publish
💣 坑 3:Transients 直接打 wp_options——autoload 体积爆炸
set_transient('my_key', $data, 3600) 在没有 Object Cache 的情况下,会写入 wp_options 表,字段格式是 _transient_my_key + _transient_timeout_my_key。问题是 **每次 set 都会带上 autoload=yes**,如果你每分钟写一次,wp_options 的 autoload 体积从 4MB 膨胀到 28MB 是几小时的事。
6/29 那篇 wp-config 优化里我也提过 wp_options autoload 清理,但这次踩到的是**新站**:一个会员站用了 15 个 shortcode widget,每个 widget 都 set_transient 一份 200KB 的 JSON 配置。
-- 抓凶手(SQL 上 wp-admin 的 phpMyAdmin 也能跑):
SELECT option_name, LENGTH(option_value) AS bytes
FROM wp_options
WHERE option_name LIKE '_transient_%'
AND option_name NOT LIKE '_transient_timeout_%'
ORDER BY bytes DESC LIMIT 20;
-- 看到 _transient_widget_config_json 占了 198,876 bytes × 3 份
修复:这种 widget 缓存数据根本不该走 transients,应该走 wp_options 的非 autoload 字段或换 wp_cache_*(前提是你装了 Object Cache Pro)。transients 只适合:
| 适合 | 不适合 |
|---|---|
| 跨页面共享的小数据(<10KB) | widget 配置(>50KB) |
| 短期 + 高频读取(rate limit token) | API 响应的整包 JSON(>200KB) |
| 已有 Object Cache 的中等数据 | 必须存数据库的元数据(应走 wp_postmeta) |
三个具体修复
修复 A(一次性清理):
# 用 wp-cli 批量删掉超过 1MB 的 transients(需谨慎,建议先看列表):
wp db query "SELECT CONCAT('DELETE FROM wp_options WHERE option_id = ', option_id, ';') AS q FROM wp_options WHERE option_name LIKE '_transient_%' AND LENGTH(option_value) > 1048576\G" | grep "^q$" | sed 's/q//' | wp db query
# ⚠️ 上面的两段 query 是分离的,先 dry-run 再真删
修复 B(源头改造——long transients 走 wp_postmeta):
// 把 200KB 的 widget 配置从 transients 搬到 postmeta
update_post_meta($widget_id, '_config_cache', $json_data, false);
// 过期判断逻辑由你控制(自定义 cron cleanup)
修复 C(Object Cache Pro 优先方案):
如果站已经有 Redis Object Cache Pro,transients 自动走 Redis,不会再膨胀 wp_options。验证:
wp transient list --network 2>/dev/null | head -10
# 应看到 store 字段都是 redis_object_cache
💣 坑 4:Object Cache Pro + WP 7.0 单飞锁 race condition(Trac #62757)
7.0 引入的 wp_run_scheduler() 单飞锁机制在某些场景下会和 Object Cache Pro 的 Redis 连接池冲突:
# 触发条件:
# 1. WordPress 7.0.0(早期 7.0,没升级到 7.0.1)
# 2. 装了 Redis Object Cache Pro 1.21.0+
# 3. 流量峰值时段(每秒 > 50 个 cron spawn 触发)
# 现象:定时发布任务进入 wp-cron.php 后,挂在 Redis SELECT 命令上 30 秒才返回
# PHP-FPM worker 全部卡住,触发 504 Gateway Timeout
我去查了 Trac:这是 Trac #62757 "Race condition in cron spawn lock when Object Cache backend is slow to respond",**7.0.1(2026-07-09 发布)已经修复**:
- 7.0.1 release 由 Aaron Jorbin / Brian Haas / Carlos Bravo / Estela Rueda 领衔,修了 31 个 bug(wordpress.org/news/2026/07/wordpress-7-0-1-maintenance-release/)
- 修复要点:cron 锁改为 process-local,先用 `get_option()` fallback 再用 Object Cache
- PR #12526 是 #62757 的 GitHub mirror
修复方法很简单:升级到 7.0.1:
wp core update --minor
# 或者手动 wp core update-db
升级验证:
wp core version
# 应输出 7.0.1
wp eval 'global $wp_version; echo $wp_version . PHP_EOL;'
# 应输出 7.0.1
⚠️ 为什么这坑特别隐蔽:Object Cache Pro 默认连接池 5 秒超时,平时根本看不出。一旦 Object Cache 后端(Redis)短暂 502,整个 cron 系统就会被 stuck 30 秒。我自己站上这个坑被 7.0.1 修掉,但 7.0 + Object Cache Pro 的组合在 2026-06-11 升级时没踩到是因为流量还没起来。
💣 坑 5:Redis 满后 Transients 全回落 wp_options——缓存命中率归零零告警
最恐怖的一种生产事故:Object Cache Pro 配置 maxmemory-policy=allkeys-lru,Redis 满 6GB 后,新写入开始淘汰旧 key,这个行为 OK。但 **Transients 落到 Redis 之后,Redis 一重启或满到极限,会进入降级模式:所有 transient get/set 全部回落到 wp_options**。
# 现象:
wp transient get my_key
# 输出 (bool) false ← 看似缓存未命中
# 但实际上 key 应该存在
**为什么会这样**?Redis Object Cache Pro 1.21+ 引入了 redis_cache_fallback_to_db 的新参数:默认 true,意味着 Redis 短暂无响应时会降级到 wp_options。这一设计的初衷是好的(容错),但**没有告警**:
# 抓告警的 wp-cli 方式
wp eval 'var_dump(wp_using_ext_object_cache());'
# 输出 bool(true) ← 看着没问题
# 但 wp-content/object-cache.php debug 日志
tail -f wp-content/object-cache.php
# 应看到:
# Redis not available, falling back to internal cache (3x in 60s)
如果没有这一行日志抓告警,你永远不会知道降级正在发生。
修复方案:
# 方案 A:wp-config.php 里直接禁用降级
# 强制 Redis 不可用就报错,而不是悄悄降级
define('WP_REDIS_DISABLE_DB_FALLBACK', true);
# 方案 B:加 Redis 监控(推荐)
# 装 New Relic / Uptime Kuma 监控 Redis 内存使用率 > 80% 报警
redis-cli info memory | grep used_memory_human
# used_memory_human:5.74G ← 已经接近 6GB 上限
# 方案 C:定期把长时间没访问的 transients 重写到 wp_options
# 这是 Redis 自己的 maxmemory-policy 范围,按下面配合使用
redis-cli config set maxmemory-policy 'allkeys-lru'
redis-cli config set maxmemory '6gb'
⚠️ **关键**:WP_REDIS_DISABLE_DB_FALLBACK = true 会让 Redis 真的挂掉时,transient set 抛 fatal error。要权衡:如果你的 Redis 是单点(没有 Sentinel/Cluster),不要开;如果有 Redis Sentinel HA,可以开。
🛡️ 7.0 黄金配置:wp-config.php 4 项 + Object Cache Pro 3 项
// wp-config.php 里任务层 + 缓存层的最小可生产配置
// 1. WP-Cron 单飞锁
define('WP_CRON_LOCK_TIMEOUT', 60);
// 2. 不在前端触发 WP-Cron(必须配系统 crontab!)
define('DISABLE_WP_CRON', true);
// 3. Object Cache 后端可选锁
define('WP_REDIS_DISABLE_DB_FALLBACK', false); // 默认 false,按需打开
// 4. WordPress 7.0.1+ 默认的 retry-after 解析(7.0 早期版本需要手工启用)
// 7.0.1 修了 Trac #62384,retry-after header 自动解析,无需手动配置
Object Cache Pro 1.21+ 的 wp-content/object-cache.php 里:
// 5. Cron 锁优先 Redis
$redis_cache_cron_lock = true;
// 6. transients 自动走 Redis
$redis_transient_groups = ['default', 'wc_sessions', 'popularity'];
// 7. 监控钩子
add_action('redis_cache_failure', function($error) {
error_log('[Redis cache failure] ' . $error);
// 接到 Slack / Sentry / 邮件告警
});
📋 5 项生产验证 Checklist
升级到 7.0.1 之后跑这套:
# 1. cron 锁工作正常
wp eval '
global $wp_filter;
echo defined("WP_CRON_LOCK_TIMEOUT") ? WP_CRON_LOCK_TIMEOUT : "NOT_DEFINED";
echo PHP_EOL;
echo get_transient("doing_cron_test") . PHP_EOL;
'
# 2. 定时发布功能
wp post create --post_type=post --post_title='cron test' --post_status=future \
--post_date='2026-07-15 11:00:00'
# 等到 11:00 后查
wp post list --post_status=publish | grep "cron test"
# 3. Object Cache 命中率
wp eval '
$obj = wp_cache_get( "stats", "object_cache_stats" );
if ( ! $obj ) return "no stats yet";
print_r( $obj );
'
# 应输出 hits > 100 且 misses < hits
# 4. Transients 在 Redis 里
redis-cli keys '_transient_*' | head -10
# 应有非空输出
# 5. wp_options autoload 体积
wp db query "SELECT SUM(LENGTH(option_value)) FROM wp_options WHERE autoload='yes'"
# 应 < 2MB(健康范围:< 4MB)
5 项全过 → 生产就绪。任何一项不过,回到对应章节修。
🧠 FAQ
Q: WP-Cron 跑得慢,影响前端吗?
A: 7.0 起默认不会——cron spawn 是 wp_remote_post 非阻塞 HTTP,前端页面立即返回。除非你装 Object Cache Pro + Redis 短暂 502 的特殊 race condition(Trac #62757),正常情况下零影响。
Q: DISABLE_WP_CRON 之后没配系统 crontab,定时任务会丢吗?
A: 不会丢,任务存在 wp_options.cron 字段里,只是不会被触发。配好 crontab 后会一次性补跑(wp cron event run --due-now)。
Q: Transients 和 wp_cache_* 区别是什么?
A: set_transient() 是 WordPress 公开 API,会根据是否启用 Object Cache 自动选择存储位置(Redis 或 wp_options)。wp_cache_set() 直接调用 Object Cache,没有 wp_options 兜底。两者的代码可读性不同——业务代码用 transient 兼容性更好;底层框架代码用 wp_cache_* 更精准。
Q: 7.0 升级到 7.0.1 之前会有什么风险?
A: 主要是 Trac #62757 race condition,会导致定时发布卡 30 秒。在 WordPress 7.0 早期版本(2026-05 ~ 06)+ Redis Object Cache Pro 1.21+ + 高流量站点组合下表现明显。7.0.1 默认开自动 minor update,5/15 后应已升级。
Q: 我能完全不用 WP-Cron,全部用 Linux cron 吗?
A: 可以。wp cron event run --due-now 就是为这个设计的,但要注意:插件注册的 hook(Yoast SEO sitemap、Akismet 反垃圾扫描、WP Crontrol 监控)全部跑在 WP-Cron 上,不走系统 cron 等于这些 hook 不工作。除非你真的能列出所有 hook 并自己跑,否则不推荐。
📚 下一篇是什么
下一篇是同一系列的"WP-Cron 可观测性"——讲怎么用 wp cron event list --format=json + Slack bot 实现定时任务的漏跑告警,避免"看起来 WP-Cron 在跑但实际某个 hook 卡 3 天"的现象。
相关阅读
👉 Join MiniMax Token Plan: AI coding acceleration for businesses
👉 Join Zhipu Coding Plan: GLM-4.6/GLM-5 coding packages, China-stable, pay-per-token unlimited
👉 Join Aliyun AI: Top AI products with exclusive coupons for business innovation
📌 This article was AI-assisted generated and human-reviewed | TechPassive — An AI-driven content testing site focused on real tool reviews
🔗 Recommended Tools
These are carefully selected tools. Using our affiliate links supports us to keep producing quality content: