WordPress 每个页面都查 200 次数据库:用 Redis 对象缓存把它砍到个位数
如果你用 SHOW FULL PROCESSLIST 或 Query Monitor 插件观察过一个未优化的 WordPress 站,会发现一个惊人的事实:打开一个首页,可能产生几百次 SQL 查询,而其中绝大多数查的是同一批数据——wp_options 里的自动加载项、菜单、小工具配置、分类列表。这些数据在几百个请求之间几乎不变,却每次都从 MySQL 里重新拼装一遍。
对于个人站长的低配 VPS(1 核 1G 或 2 核 2G),MySQL 往往是第一个被打满的进程。解决思路不是给数据库加内存,而是把"重复读取的、变化不频繁的"数据放进 Redis。这个机制在 WordPress 里叫 Object Cache(对象缓存),官方只提供了内存级(单次请求有效)的实现,跨请求的持久对象缓存需要 redis-object-cache 这类 drop-in 来接管。
先确认问题存在,再谈优化
不要盲目上 Redis。先量化:
# 看某个页面的实际查询次数,装 Query Monitor 插件最直接
# 或者用 MySQL 侧观察慢查询与总 QPS
mysqladmin -uroot -p extended-status | grep -E 'Com_select|Queries'
# 隔 10 秒再看一次,差值就是这 10 秒的查询量如果 Queries/秒 长期在几百以上,而站点访问量并不大(比如日 PV 几千),那基本可以确定为重复查询过多,对象缓存收益会很明显。反之,如果 QPS 本身很低,Redis 的收益有限,不如先做页面缓存。
安装 Redis 与 PHP 扩展
Debian/Ubuntu:
apt install -y redis-server php-redis
systemctl enable --now redis-server
redis-cli ping # 返回 PONG 即成功关键点:确认你的 PHP 版本对应正确的扩展包。很多宝塔/lnmp 环境里 PHP 是自定义路径的,得用对应版本的安装方式:
# 查看当前 PHP 是否已加载 redis
php -m | grep redis
# 若为空,检查 php-fpm 用的 php.ini 与扩展目录
php -i | grep -E 'Loaded Configuration|extension_dir'如果 php -m 有 redis,但 WordPress 后台仍提示 "Redis extension not installed",说明命令行 PHP 和 php-fpm 用的不是同一套扩展。要用 php-fpm 对应的那个 php 二进制验证,或者直接写个探针文件:
<?php var_dump(class_exists('Redis')); ?>
# 放到网站根目录访问一次,输出 bool(true) 才算 OK,记得用完删掉Redis 内存策略:别让它把 VPS 吃干
Redis 默认没有内存上限,在 1G 内存的小机器上很容易把系统拖死。上线前必须改 /etc/redis/redis.conf:
# 限制最大内存,建议给系统留足余量:1G 机器给 128M~256M
maxmemory 256mb
# 淘汰策略:对象缓存场景用 allkeys-lru 最合适
# 它会在内存满时淘汰最久未使用的键,而不是拒绝写入
maxmemory-policy allkeys-lru
# 持久化:对象缓存是"可再生"数据,关掉 RDB/AOF 反而更安全更快
save ""
appendonly no
# 绑本地,不对外暴露
bind 127.0.0.1 ::1
protected-mode yes
requirepass 一个强密码 # 即使本地也建议设为什么要关持久化?因为对象缓存的内容随时可以从 MySQL 重建。开着 RDB 会让 Redis fork 子进程做快照,小内存机器上 fork 的瞬间可能出现明显卡顿,而收益只是省下一次重建时间。这是很多"装了 Redis 反而变慢"案例的真实原因。
改完重启并验证:
systemctl restart redis-server
redis-cli info memory | grep -E 'maxmemory_human|used_memory_human|maxmemory_policy'安装 drop-in 并让 WordPress 认账
推荐使用 redis-object-cache(原 WP Redis 的继任者),有两种安装方式:
- 插件方式:后台上传启用,然后按提示执行 "Enable Object Cache",它会自动把
object-cache.php复制到wp-content/。 - 手动方式:把
object-cache.php直接放到wp-content/目录。
验证 drop-in 已生效:
ls -l /www/wwwroot/你的站/wp-content/object-cache.php
# 文件存在即已接管
# 配置连接信息(若用 wp-config.php 定义)
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('WP_REDIS_PASSWORD', '你的强密码');
define('WP_REDIS_DATABASE', 0);
# 键前缀,多站共用一个 Redis 时务必区分
define('WP_CACHE_KEY_SALT', 'zz1984:');
define('WP_REDIS_MAXTTL', 86400);WP_CACHE_KEY_SALT 是多站点场景的一道保险,忘了设的话,同一个 Redis 里的不同站点会互相读到对方的缓存,出现"改了这个站的设置,另一个站变了"这种灵异问题。
看命中率:优化有没有真正生效
生效后,用后台的 "Object Cache" 诊断页可以看到 Hits / Misses,或者用 redis-cli 直接看:
redis-cli info stats | grep -E 'keyspace_hits|keyspace_misses'
# 命中率 = hits / (hits + misses),健康值应在 90% 以上
redis-cli info keyspace
# db0:keys=xxxx,expires=xxxx,avg_ttl=0
# 随机看几个键,确认前缀正确、内容合理
redis-cli --scan --pattern 'zz1984:*' | head -20如果命中率低于 80%,按以下顺序排查:
- Keys 数量持续暴涨:可能是某插件每次请求都写带随机后缀的键(缓存穿透式写入)。用
redis-cli --scan找规律,再决定是否拉黑该插件。 - maxmemory 太小说到上限:键被频繁淘汰,命中率自然上不去。用
redis-cli info stats | grep evicted看淘汰次数,若数字很大就调大 maxmemory。 - 每次访问 keys 数都变:说明缓存根本没被复用,通常是 drop-in 没生效,或 PHP 每次请求连的是不同的 Redis 实例。
与页面缓存的分工,别混为一谈
对象缓存和页面缓存是两层不同的东西,很多人混用导致优化无效:
- 对象缓存(Redis):缓存 PHP 内部的数据对象,访问者看到的仍然是动态生成的 HTML。对已登录用户、购物车、个性化页面有效。
- 页面缓存(Nginx fastcgi_cache 或插件):直接缓存最终 HTML,命中时完全不执行 PHP。对未登录访客效果最佳。
个人站的最佳组合通常是:Nginx fastcgi_cache 打头阵处理未登录访客,Redis 对象缓存处理已登录/动态部分,MySQL 只承担真正的写入和回源。三者监控指标分别是:Nginx 缓存命中率、Redis 命中率、MySQL QPS。优化前记下这三个数,优化后再对比,收益一目了然。
运维注意:失效、清理与降级
Redis 挂掉时,WordPress 会怎样?取决于 drop-in 的容错。多数实现会在连不上时自动降级回数据库直查,站点只是变慢,不会报错——这是理想行为,但也意味着Redis 悄悄挂了很久你可能都不知道。要主动监控:
# 简单探活脚本,配合 cron 每 5 分钟跑一次
#!/bin/bash
if ! redis-cli ping > /dev/null 2>&1; then
echo "[告警] Redis 无响应 $(date) on $(hostname)" >> /var/log/redis_alert.log
# 可在此接邮件/Telegram 通知
fi另外,修改主题、插件、菜单后如果发现前台显示旧内容,先清缓存再判断是否是真 bug。Redis 里要清单个站点的键,不要粗暴 FLUSHALL——多站共用时会把别的站也清掉:
# 按前缀清理(用 scan 逐批删除,避免阻塞)
redis-cli --scan --pattern 'zz1984:*' | xargs -r -n 100 redis-cli del把 Redis 用对,对个人站长而言就是把低配 VPS 的可用性往上抬一档:MySQL 负载下降、页面响应变快、突发流量更容易扛住。整个过程的核心只有三件事——限量内存、锁定本地、盯住命中率。做到这三点,Redis 就是一个安静且可靠的基础设施,而不是又一个半夜把你叫醒的麻烦源。