「感觉快了不少」不是优化
升级 PHP 是一件很容易获得「成就感」的事:命令行敲完,页面打开,主观感觉快了,于是收工。问题是这种感觉有两个毛病——你不知道快了百分之几,也不知道是不是别的东西同时变了。下次再卡,你还是没有参照物。
这篇文章里的数字全部来自一台真实在跑的 WordPress 站:2 核 3.6 GB 的云主机,宝塔面板,升级前后用同一套方法量。方法只有 15 行代码,任何人都能复制。
先建一张底数表
优化前必须先知道「现在是多少」,否则改完无法判断是不是白改。这台机器的基线:
| 项 | 值 |
|---|---|
| 服务器 | 2 核 / 3.6 GB(实际可用 2.3 GB) |
| 系统 / 面板 | Ubuntu 22 + 宝塔 |
| Web 服务器 | Nginx 1.30.5,http2 on,gzip 已开 |
| PHP | 8.2.35(升级前 7.4) |
| 数据库 | MySQL 5.7.43 |
| 程序 | WordPress 7.1.4 + 子比主题 |
| 页面体积 | 首页 HTML 未压缩 73 KB → gzip 后 16 KB(压缩率 78%) |
| SQL 条数 | 首页 25 条、文章页 44 条、搜索结果页 29 条 |
最后一行不是装饰:后面所有「为什么还是慢」的问题,都要回到这里找答案。
坑一:面板里切了 PHP 版本,其实没生效
宝塔的「网站 → 设置 → PHP 版本 → 切换」只改数据库里记录的那个值,真正决定用哪个 PHP 的是 nginx 站点配置里的一行:
fastcgi_pass unix:/tmp/php-cgi-74.sock;
切换版本后这行常常还是旧的。当时实测 grep -c 'php-cgi-74' 结果是 5,php-cgi-82 是 0——站点在面板里显示 8.2,实际仍然跑 7.4。
正确做法是直接改配置文件里的 socket 名,然后 nginx -t && nginx -s reload。但光看配置还不够,因为配置也可能被别的地方覆盖。最终验证一定要走 Web:写一个临时文件,用 HTTP 访问它,看它自己报的版本。
<?php
echo 'ver=' . PHP_VERSION
. ' mem=' . ini_get('memory_limit')
. ' opcache=' . ((function_exists('opcache_get_status') && opcache_get_status(false)['opcache_enabled']) ? 'ON' : 'OFF');
curl -sk https://127.0.0.1/_v.php -H 'Host: 你的域名'
注意必须走 HTTPS 加 -k:站点强制 HTTPS 的时候,HTTP 请求会被 301 掉,拿不到内容。测完立刻删掉这个文件。
坑二:CLI 查不到你改过的 PHP 配置
宝塔的 PHP 有两套 ini:命令行读 php-cli.ini,FPM 读 php.ini,都在 /www/server/php/版本/etc/ 下。你在终端里敲 php -v、php -i 看到的,和你网站实际运行的可能是两个世界。
这次就是:终端里显示 memory_limit = 128M,网站上跑的是 512M。所以验证 FPM 的真实参数,只能用上面那个走 Web 的临时文件——它输出的 sapi=fpm-fcgi 才是真话。
坑三:OPcache 默认是注释掉的
PHP 8.2 安装包里 opcache.so 是有的,但 php.ini 里这两行默认带着分号:
;zend_extension=opcache
;opcache.enable=1
没放开就等于没装。放开的实际配置:opcache.memory_consumption=192、opcache.max_accelerated_files=10000、opcache.validate_timestamps=1、opcache.revalidate_freq=2、opcache.jit=tracing。改完要 重启 php-fpm,配置文件是进程启动时读的,只 reload 不一定吃到新值。
怎么量:15 行探针
把下面这个文件丢进 wp-content/mu-plugins/,mu-plugin 不需要在后台启用就会自动加载,测完删掉即可:
<?php
add_action('shutdown', function () {
global $wpdb;
$st = function_exists('opcache_get_status') ? @opcache_get_status(false) : null;
$hit = $st['opcache_statistics']['opcache_hit_rate'] ?? -1;
printf("n<!--PROBE q=%d t=%ss mem=%.1fM opcache=%s hit=%.1f%%-->",
get_num_queries(),
timer_stop(0, 3),
memory_get_peak_usage(true) / 1048576,
(!empty($st['opcache_enabled']) ? 'on' : 'off'),
$hit
);
}, 999);
它会在每个页面的 HTML 末尾追加一行注释,内容是:SQL 条数、WordPress 自身渲染耗时、进程峰值内存、OPcache 开关和命中率。用 curl 抓回来就能看到:
curl -s https://你的域名/ | grep -o '<!--PROBE[^>]*-->'
# --PROBE q=25 t=0.168s mem=12.0M opcache=on hit=99.8%--
这条注释对访问者是隐形的,但会跟着缓存和 CDN 走,所以测完要删文件。
实测 A/B:关掉 OPcache,再开回来
为了确认这 300 毫秒到底是谁省的,做了一次对照:先在 php.ini 里把 opcache.enable 改成 0,重启 php-fpm,连续请求首页 6 次;再改回 1 重启,等缓存热起来后连测 8 次。取中位数:
| 指标 | OPcache 关 | OPcache 开(预热后) | 变化 |
|---|---|---|---|
| PHP 生成耗时 | 519 ms | 179 ms | 快 2.9 倍 |
| 首字节 TTFB | 842 ms | 493 ms | 少 349 ms |
| 单请求峰值内存 | 60.5 MB | 12 MB | 降到 1/5 |
| 命中率 / 缓存脚本 | — | 99.75% / 817 个 | 占用 52.9 MB(上限 192 MB) |
内存那一行比耗时更有意思:关掉 OPcache 时,每次请求都要把 PHP 文件重新编译成 opcode,这份编译产物只能放在请求自己的内存里,所以单请求峰值从 12 MB 涨到 60.5 MB。2 核机器上 PHP-FPM 开十几个进程,光编译产物就要吃掉几百 MB——内存不够、swap 一开,慢就不只是编译的事了。
三个反直觉的细节
一、重启后的第一个请求最慢。刚重启 php-fpm,第一次请求要现场编译全部 817 个脚本,实测生成耗时 947 ms、TTFB 1.4 s,比关掉 OPcache 还慢。之后就稳定在 0.17 s 左右。
二、命中率不是一开始就 99%。重启后连续 8 次请求,命中率是 0.44% → 50% → 66% → 75% → 80% → 83% → 86% → 87.6% 这样爬上去的。所以重启完立刻测性能,结论一定是错的,至少跑十几分钟再把命中率当数据用。
三、字段名会骗你。opcache_get_status() 返回的统计数据里,命中率的键叫 opcache_hit_rate,不叫 hit_rate。我第一次写成 hit_rate,探针老老实实报了「命中率 0%」,差点去查一个并不存在的问题。看到异常值先怀疑测量代码,再怀疑系统。
现在还剩什么没优化
把 PHP 生成压到 179 ms 之后,TTFB 仍有 493 ms,剩下的部分拆开看是这样(同一次测量):
- TLS 握手约 199 ms —— 新连接才有,复用连接时省掉
- PHP 生成 179 ms —— 已经优化过
- 剩下的约 110 ms —— Nginx 处理、本机到客户端的网络往返
也就是说,动态页面再怎么优化 PHP,也压不过一个纯静态文件。同一台机器上,静态的 /tools/ 页面 TTFB 是 230 ms,动态首页 493 ms,差了一倍多。
再看两个还没做的点:
响应头告诉别人别缓存。首页返回的是:
Cache-Control: no-store, no-cache, must-revalidate
no-store 比 no-cache 更狠——它连存都不让存。浏览器每次都要回源,将来接 CDN 也会被这一行卡住。动态页面应该用 no-cache(可以存、必须校验),而不是 no-store。
没有页面缓存层。站点配置里没有 fastcgi_cache,每个请求都真的执行一遍 WordPress。文章页 44 条 SQL,就算每条只要 1 毫秒,也是 44 毫秒。上 Nginx 的 fastcgi_cache 之后,未登录访客命中缓存时 PHP 完全不参与,TTFB 可以直接掉到静态文件的水平。
这两件事的收益比「把 PHP 从 8.2 升到 8.3」大得多——后者几乎没有可测差异。
顺序对了,优化才有意义
回头看这次升级,真正有价值的不是「从 7.4 换到 8.2」这个动作,而是把过程变成了可复现的测量:先记底数(SQL 条数、生成耗时、TTFB),改一个变量,复测同样的指标。
如果你也要做同样的升级,按这个顺序:确认 FPM 真的换了版本 → 放开 OPcache 并重启 → 装 15 行探针记底数 → 等命中率稳定后再测 → 最后才是考虑页面缓存。反过来做(先上缓存插件再升级)的人,永远说不清到底是哪一步起了作用。
后续:这一篇是在「让 PHP 跑得更快」上做文章。后来我又给站点加了 Nginx 页面缓存,同一个地址第二次请求的 TTFB 从 254 ms 降到 46 ms,PHP 进程根本不用启动。完整配置、跳过规则和三个副作用写在给 WordPress 上 fastcgi_cache里。










暂无评论内容