后台点一下菜单要等两三秒,前台却看着还行——这种「只有登录后才慢」的症状,绝大多数是 wp_options 表里的 autoload 数据涨太大了。这张表有个特性:所有 autoload='yes' 的记录,每加载一个页面都会被整体读出来塞进内存,不管你这个页面用不用得上。数据涨到 1MB 以上,等于每次请求先白拉 1MB 垃圾,加多少缓存插件都救不回来。
先量一下你的 autoload 有多大
别凭感觉。进数据库跑一条 SQL,30 秒出结论:
SELECT SUM(LENGTH(option_value)) AS autoload_bytes,
COUNT(*) AS autoload_rows
FROM wp_options
WHERE autoload = 'yes';
没装 phpMyAdmin 的用 WP-CLI 更省事:
wp db query "SELECT ROUND(SUM(LENGTH(option_value))/1024) AS kb, COUNT(*) AS rows FROM wp_options WHERE autoload='yes';" --allow-root
表前缀不是默认 wp_ 的记得替换。拿到数字后按下面这张表定位自己在什么档:
| autoload 体积 | 状态 | 该做什么 |
|---|---|---|
| < 300 KB | 健康 | 别折腾,去查别的原因 |
| 300–800 KB | 偏大 | 清 transient,留观 |
| 800 KB–1 MB | 拖慢明显 | 逐条排查大记录 |
| > 1 MB | 病态 | 必须动手清理 |
行数也值得看一眼。健康站点 autoload 一般在 150 到 400 行之间,冲到两三千行说明某个插件在往里面写循环数据。
找出是谁在往里面塞东西
体积超标之后,下一步是揪出最占地方的那几条记录,而不是无脑跑「一键优化」。
SELECT option_name,
ROUND(LENGTH(option_value)/1024, 1) AS kb
FROM wp_options
WHERE autoload = 'yes'
ORDER BY LENGTH(option_value) DESC
LIMIT 20;
典型输出长这样:
+------------------------------------+-------+
| option_name | kb |
+------------------------------------+-------+
| rewrite_rules | 186.4 |
| xyz_slider_cache_all | 142.7 |
| _elementor_global_css | 98.2 |
| jetpack_available_modules | 61.5 |
| wpseo_titles | 22.8 |
+------------------------------------+-------+
看到 option_name 里带 cache、_transient_、_log、_queue 字样又占几十 KB 的,基本都是插件把临时数据错误地写成了 autoload。rewrite_rules 涨到一两百 KB 通常是自定义文章类型和分类太多,这条不能删,但可以在「设置 → 固定链接」点一次保存让它重建,多余的旧规则会被刷掉。
安全的清理顺序
动手前先备份,这一步不能省:
wp db export backup-before-autoload-$(date +%F).sql --allow-root
接着按风险从低到高来:
第一步用 WP-CLI,比手写 SQL 稳:
wp transient delete --expired --allow-root
wp cache flush --allow-root
第三步是很多人不知道的做法。有些数据你不敢删,但它完全没必要每次请求都加载,那就把开关关掉、把数据留着:
UPDATE wp_options
SET autoload = 'no'
WHERE option_name = 'xyz_slider_cache_all';
这比 DELETE 安全得多。改错了大不了改回 'yes',数据一直在那儿。
踩坑实录
有个站 autoload 一直卡在 1.4 MB,清完 transient 只掉到 1.35 MB。排下来最大一条是 _wc_session_expired 之外的 wc_tracker_last_send,才 3 KB,看着都不像凶手。后来把 ORDER BY 换成 COUNT 分组才看明白:SELECT COUNT(*) FROM wp_options WHERE option_name LIKE '\_transient\_timeout\_%' 返回 21400 行。一个已经卸载的抓取插件留下两万多条永不过期的 transient,单条只有几十字节,加起来把表撑爆了。wp transient delete --expired 对它没用,因为这些记录压根没设过期时间。最后是按前缀定向删掉的,删完 autoload 降到 260 KB,后台响应从 2.1 秒回到 0.4 秒。所以别只看单条大小,行数异常同样要查。
清完之后怎么防止复发
清理是一次性的,源头不堵住三个月又会涨回来。三件事值得做:
把 object cache 装上。Redis 或 Memcached 接进来之后,transient 会走缓存而不是数据库,wp_options 直接安静下来。这是最省事的根治手段。
删插件时用后台的「删除」而不是 FTP 删目录。走正常卸载流程会触发 uninstall 钩子清自己的数据,直接删文件夹等于把垃圾永久留在数据库里。
定期抽查。把上面那条 SUM 查询存成一行命令,每月跑一次,超过 500 KB 就查一遍最大记录。
# 加进 crontab,每月 1 号把体积写进日志
0 3 1 * * cd /var/www/html && wp db query "SELECT ROUND(SUM(LENGTH(option_value))/1024) FROM wp_options WHERE autoload='yes';" --allow-root >> /var/log/wp-autoload.log
常见问题
问:直接 DELETE 掉那些 cache 类记录会不会出事?
option 名字里明确带 cache、transient 的可以删,插件会重新生成。但设置类的(比如 wpseo_titles、elementor_*_settings)删了等于配置清零,这类只能改 autoload 开关,不能删。
问:装个数据库优化插件一键清理,比手动靠谱吗?
清过期 transient 这一块插件做得没问题。但它们不会判断哪条 autoload 该关、哪条该留,遇到我上面那种「两万条无过期时间的僵尸记录」也常常识别不出来。体积超过 1 MB 时还是得自己看一眼 SQL 输出。
问:为什么前台不慢只有后台慢?
前台大概率被页面缓存挡住了,请求根本没进 PHP。后台不能缓存,每次都实打实读一遍 autoload,所以症状先在后台暴露。
把你站点的 autoload 体积和最大那几条 option_name 发到 fenij.com 的评论区,我可以帮你判断哪些能动。