"网站慢"是一个模糊的表述,它可能意味着十几种完全不同的问题:用户点了链接三秒才出白屏,也可能页面很快出来了但图片一张张慢慢刷出来,还可能是只有部分地区用户慢、或者只有高峰期慢。这些情况对应的原因和优化手段完全不同,如果只是凭感觉去"优化",很容易把力气花在不相干的地方。
这篇文章给一套分层排查的方法:先用工具把慢的时间量化和分解,定位到具体是哪一层慢,再针对那一层找瓶颈。每一层都给出可执行的诊断命令和典型的修复思路,最后给出优化投入的顺序建议。
先别猜,先量化
排查的第一原则是:把"慢"变成一个数字,并把这个数字拆成几段。
一个 HTTP 请求从发起到完成,时间可以拆成 DNS 解析、TCP 连接、TLS 握手、服务端处理、内容传输这几段。搞清楚哪一段长,问题就缩小到一个具体方向。
用 curl 做时间分解
curl 的 -w 参数可以输出各阶段耗时:
curl -o /dev/null -sS -w 'DNS解析: %{time_namelookup}s\n\
TCP连接: %{time_connect}s\n\
TLS握手: %{time_appconnect}s\n\
开始传输: %{time_starttransfer}s\n\
总耗时: %{time_total}s\n\
下载速度: %{speed_download} B/s\n' https://www.example.com/
各字段的读法:
| 指标 | 含义 | 慢说明什么 |
|---|---|---|
time_namelookup |
DNS 解析耗时 | DNS 服务器慢或域名链路配置有问题 |
time_connect |
TCP 连接建立完成(含 namelookup) | 网络时延高或丢包重传 |
time_appconnect |
TLS 握手完成 | TLS 握手慢、证书链校验慢、HTTP/2 未启用 |
time_starttransfer |
收到第一个字节(约等于 TTFB) | 服务端处理慢,重点查应用与数据库 |
time_total |
全部完成 | 若远大于 starttransfer,说明内容传输慢,查资源体积与带宽 |
一个实用的判断方法:time_starttransfer 减 time_appconnect 大致就是服务端生成页面的时间。如果这一项超过几百毫秒,说明瓶颈在服务端(应用或数据库),而不是网络。
用浏览器 DevTools 看整体
curl 只看单个请求,页面整体表现要看浏览器的网络瀑布图。打开开发者工具的 Network 面板,刷新页面,重点看:
请求数量。 几十个小资源的请求在 HTTP/1.1 下会串行排队,启用 HTTP/2 能显著改善。
是否有请求在排队(Queueing / Stalled)时间长。 这通常说明浏览器对同一域名的并发连接数达到上限(HTTP/1.1 一般是六个左右)。
TTFB(Waiting for server response)。 服务端响应时间,与 curl 的 time_starttransfer 对应。
Content Download 占比。 如果下载时间远大于等待时间,说明资源体积过大。
DOMContentLoaded 与 Load 的时间差。 Load 明显更晚说明是图片、字体、第三方脚本拖慢了。
底部还有一行汇总:传输量、未压缩大小、请求数、完成时间。如果传输量与资源大小接近,说明没有启用压缩。
用 ping 和 mtr 看网络质量
ping -c 20 www.example.com
关注平均延迟和丢包率。丢包哪怕只有百分之几,也会因为 TCP 重传导致体验明显下降。
要定位是哪一段网络有问题,用 mtr(Debian、Ubuntu 上是 mtr-tiny 或 mtr 包,RHEL 系是 mtr 包):
sudo apt install -y mtr # Debian、Ubuntu
sudo dnf install -y mtr # Rocky、RHEL、CentOS
mtr -rwc 100 www.example.com
mtr 结合 traceroute 和 ping,能显示每一跳的延迟与丢包。哪一跳开始丢包或延迟突增,问题就在那段链路上。这对"只有部分地区用户慢"的问题特别有效。
分层排查框架
把可能的原因按层组织,逐层验证:
| 层级 | 典型症状 | 关键工具 |
|---|---|---|
| DNS | time_namelookup 大;首次访问慢后续快 |
dig、curl -w |
| 网络 | time_connect 大;有丢包;部分地区慢 |
ping、mtr、ss |
| 服务器资源 | 所有站点一起慢;SSH 也卡 | top、free、iostat、vmstat |
| Web 服务 | TTFB 高但资源空闲;日志里 upstream 时间正常 | Nginx 日志、access 日志分析 |
| 应用 | $upstream_response_time 高 |
PHP 慢日志、应用性能分析 |
| 数据库 | 动态页面慢,静态页正常 | 慢查询日志、EXPLAIN、SHOW PROCESSLIST |
| 前端 | TTFB 正常但页面完整渲染慢 | DevTools 瀑布图 |
DNS 层
常见原因:递归 DNS 服务器响应慢、域名配置了过多 NS 记录且部分不可用、DNSSEC 校验链路有问题、使用了境外 DNS 而解析绕远路。
诊断:
dig www.example.com A
输出里的 Query time 直接给出本次查询耗时,正常应在几十毫秒以内。如果持续超过两三百毫秒,换个公共 DNS 对比测试:
dig @1.1.1.1 www.example.com A
dig @223.5.5.5 www.example.com A
dig @8.8.8.8 www.example.com A
同时检查 NS 记录数量是否合理,以及是否有 NS 指向不可用的服务器:
dig example.com NS +short
优化:确保权威 DNS 服务商稳定;合理设置 TTL,太短会增加查询频率,太长影响变更生效;国内业务优先选择在国内有节点的 DNS 服务商。
网络层
常见原因:带宽被打满、跨境链路延迟高、丢包导致 TCP 重传、MTU 不匹配导致分片。
先看带宽是否被占满:
# 安装 sar / iftop 等工具查看实时流量
sudo apt install -y sysstat iftop # Debian、Ubuntu
sudo dnf install -y sysstat iftop # Rocky、RHEL、CentOS
sar -n DEV 1 5
sudo iftop -i eth0
如果出网带宽长期接近上限,先解决的应该是"为什么流量这么大",而不是急着加带宽。 常见的流量大户:未压缩的大图片、没有缓存头的静态资源、被频繁拉取的 API 响应、以及盗链。
MTU 问题比较少见但症状独特:小请求正常,大文件或 POST 卡住。可以用 ping 测试不分片大小:
ping -M do -s 1472 www.example.com # Linux:禁止分片,测试 1500 字节 MTU
跨境访问的延迟是物理规律决定的,优化手段有限,主要靠把内容放到离用户更近的位置——这就是 CDN 的核心价值,而不是靠换机房解决。
服务器资源层
这一层出问题时的典型特征是:不只是网站慢,SSH 操作也卡。 这基本可以确定是整机资源问题。
CPU
top
htop # 更直观,需安装
关注:us(用户态)高说明是应用程序消耗;sy(系统态)高可能是系统调用频繁或上下文切换过多;wa(iowait)高说明瓶颈在磁盘 IO,而不是 CPU 本身。
负载平均值(load average)要结合 CPU 核心数看。四核机器上 load 为 4 基本是满载,为 16 说明有严重排队。
内存与 swap
free -h
vmstat 1 5
重点关注 si 和 so 两列(换入换出),只要它们持续非零,说明系统正在使用 swap,性能会断崖式下降。磁盘比内存慢几个数量级,一旦开始换页,再怎么优化代码都无济于事。
常见的内存杀手:数据库缓冲池配置过大、pm.max_children 配得太多、Java 应用堆内存设置不合理。
磁盘 IO
iostat -x 1 5
关键列:
| 列 | 含义 | 判断参考 |
|---|---|---|
%util |
设备繁忙程度 | 持续接近 100% 说明磁盘成为瓶颈 |
await |
IO 请求平均等待时间(毫秒) | SSD 上超过 10 毫秒通常偏高,机械盘几十毫秒可接受 |
r/s、w/s |
每秒读写次数 | 随机写多会明显拖慢机械盘和部分低端 SSD |
如果 %util 长期打满,多半是数据库在做大量随机写或临时表落盘。可以考虑:把临时目录和数据目录放到更快的磁盘、优化查询减少临时表、或者干脆升级存储介质。
文件描述符与连接数
ulimit -n
cat /proc/sys/net/core/somaxconn
ss -s
高并发下如果文件描述符耗尽,会出现"偶发连接失败",日志里能看到 too many open files。
Web 服务层
资源都空闲但 TTFB 还是高,就要看 Web 服务本身的配置。
用 Nginx 日志定位
先定义一个包含耗时字段的日志格式:
log_format timed '$remote_addr [$time_local] "$request" '
'$status $body_bytes_sent '
'req=$request_time upstream=$upstream_response_time '
'$http_referer "$http_user_agent"';
access_log /var/log/nginx/access.log timed;
两个关键变量的区别:
| 变量 | 含义 |
|---|---|
$request_time |
从收到请求到发送完响应,包含客户端接收慢导致的等待 |
$upstream_response_time |
只统计后端(PHP-FPM 或代理)处理的时间 |
判断方法:
upstream时间高 → 问题在后端应用或数据库upstream时间低但request_time高 → 后端很快,但传输慢或客户端慢。常见于慢速网络下的大文件传输、或者 Nginx 在等待客户端读完请求体upstream为-→ 请求没有转发给后端,可能是 Nginx 直接返回的静态文件或错误
找出最慢的请求:
awk '{ for(i=1;i<=NF;i++) if($i ~ /^req=/) print $i, $0 }' /var/log/nginx/access.log \
| sort -rn | head -n 20
按 URL 聚合统计平均耗时也很有用,能快速找出最慢的几个接口。
压缩是否启用
curl -H 'Accept-Encoding: gzip,br' -I https://www.example.com/app.js
响应头里应有 content-encoding: gzip 或 br。Nginx 配置:
gzip on;
gzip_vary on;
gzip_comp_level 5; # 1-9,越高压缩率越好但 CPU 消耗越大
gzip_min_length 1024; # 太小的文件压缩收益不明显
gzip_types text/plain text/css application/javascript application/json application/xml image/svg+xml;
# Brotli 压缩率更高,但需要安装对应模块
# brotli on;
# brotli_comp_level 5;
# brotli_types text/css application/javascript application/json;
压缩对文本类资源(HTML、CSS、JS、JSON)效果极其显著,通常能减少百分之六十到八十的体积。这是投入产出比最高的优化之一,一行配置的事。
注意不要对已经是压缩格式的图片、视频、PDF 再做 gzip,既浪费 CPU 又几乎没效果。
静态资源缓存
没有缓存策略会导致用户每次访问都重新下载所有静态资源:
location ~* \.(jpg|jpeg|png|gif|webp|ico|css|js|woff2)$ {
expires 30d;
add_header Cache-Control "public";
access_log off;
}
# 带内容哈希的构建产物可以设更长
location ^~ /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
配合 etag 或 Last-Modified 做协商缓存,二次访问的验证请求也会很轻。
其他 Web 层配置
启用 HTTP/2(多路复用解决队头阻塞)、开启 keepalive(keepalive_timeout 一般 15 到 65 秒),以及确认 worker_processes auto 按 CPU 核心数设置。这些配置项单个收益有限但叠加起来明显。
应用层
PHP-FPM 是否成为瓶颈
# 查看进程池状态(需先在 pool 配置里开启 pm.status_path)
curl http://127.0.0.1/status
在 pool 配置里启用状态页:
pm.status_path = /status
关注几个字段:活跃进程数是否长期等于 pm.max_children(如果长期持平说明进程池太小,请求在排队)、以及 listen queue 是否有积压。
同时看慢日志,request_slowlog_timeout 设为 5 秒,超过这个时间的请求会记录调用栈:
slowlog = /var/log/php-fpm/www-slow.log
request_slowlog_timeout = 5s
tail -n 50 /var/log/php-fpm/www-slow.log
慢日志会直接告诉我们"哪一行函数卡住了",这是定位应用问题最快的工具。
常见应用瓶颈
OPcache 未启用。 PHP 每个请求都要重新解析编译源码,CPU 白白消耗一部分。检查一下:
php -i | grep -E 'opcache.enable|opcache.validate'
外部 HTTP 调用。 页面里同步调用第三方 API(支付查询、天气、短信、地图),第三方慢会直接拖慢你的页面。这类调用应该设短超时、加缓存、或者改为异步。这是"页面偶发很慢但服务器资源很闲"的常见原因。
未使用缓存层。 同样的复杂计算结果每次重算。引入 Redis 或 Memcached 做对象缓存,对动态站点效果显著。
数据库层
动态页面慢而静态页快,基本可以锁定数据库。
慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = 'ON';
永久生效要写进配置文件:
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
风险提示:long_query_time 设得太小(如 0.1)在写入量大的系统上会产生大量日志,可能影响性能甚至占满磁盘。 建议从 1 秒起步,逐步收紧。
分析慢日志:
sudo apt install -y percona-toolkit # Debian、Ubuntu
sudo dnf install -y percona-toolkit # Rocky、RHEL、CentOS
pt-query-digest /var/log/mysql/slow.log | head -n 40
pt-query-digest 会把慢日志聚合成 Top N 查询,直接告诉我们应该优化哪几条 SQL。
即时查看当前在执行的语句
SHOW FULL PROCESSLIST;
看到某条语句 Time 很大且状态不是 Sleep,就是当前的阻塞源。如果大量连接卡在同一个状态(比如 Sending data 或 Locked),方向就很明确了。
锁等待排查:
SELECT * FROM information_schema.INNODB_TRX\G
SHOW ENGINE INNODB STATUS\G
常见数据库瓶颈
| 问题 | 症状 | 解决方向 |
|---|---|---|
| 缺索引 | 表数据量增长后突然变慢,rows examined 远大于返回行数 |
用 EXPLAIN 分析,补索引 |
| 索引失效 | 有索引但没走 | 检查是否在索引列上用了函数、隐式类型转换、或前导模糊查询 |
| N+1 查询 | 列表页本该一次查询却执行了几十上百次 | 改用关联查询或批量加载 |
| 大偏移量分页 | LIMIT 100000, 20 极慢 |
用游标式分页或覆盖索引优化 |
| 临时表落盘 | Created_tmp_disk_tables 持续增长 |
优化 GROUP BY、ORDER BY,适当调大临时表内存阈值 |
| 缓冲池太小 | 磁盘 IO 高,命中率低 | 调大 InnoDB 缓冲池,但要给其他服务留内存 |
| 连接池耗尽 | 应用报连接超时 | 检查连接是否正确释放,考虑连接池中间件 |
用 EXPLAIN 看执行计划是必备技能:
EXPLAIN SELECT * FROM orders WHERE user_id = 123 ORDER BY created_at DESC LIMIT 20\G
重点看 type(最好到 ref 或 range,出现 ALL 说明全表扫描)、key(是否用上了索引)、rows(扫描行数)、以及 Extra 里有没有 Using filesort 或 Using temporary。
前端层
如果 TTFB 正常(几百毫秒内)但用户感觉慢,问题几乎都在前端。
常见问题清单
图片未优化。 这是最高频的前端性能问题。一个几 MB 的 PNG 截图、直接上传的原相机照片、用 JS 加载的整屏背景图,都会吃掉几秒时间。解决:压缩(有损或无损看场景)、转为 WebP 或 AVIF 格式、按显示尺寸提供图片而不是传原图、使用响应式 srcset。
未启用懒加载。 首屏之外的图片应在滚动到附近时才加载:
width 和 height 属性要写上,避免布局抖动给用户造成"慢"的感觉。
渲染阻塞资源。 在 里的同步脚本会阻塞页面渲染。用 defer 或 async:
第三方脚本。 统计、客服、广告、社会化分享这类脚本往往是首屏最大的时间开销,它们的服务器不在你控制范围内。评估每一个第三方脚本是否真的必要,能延迟加载的就延迟。
重定向链。 例如 http://example.com → https://example.com → https://www.example.com 形成两次跳转,每次都是一个完整的往返。应该在第一步就跳到位。
字体。 大体积的字体文件会阻塞文本渲染。使用 font-display: swap 让文本先用系统字体显示,同时精简字体子集。
CDN 的作用
CDN 把静态资源缓存到离用户更近的节点,对地理距离远的用户效果最明显。启用 CDN 后要注意:正确配置缓存规则(静态资源长缓存,HTML 短缓存或不缓存)、确认资源 URL 带版本号以便更新时刷新、以及 CDN 回源本身要快(如果回源要走跨境链路,效果会打折扣)。
常见瓶颈速查表
| 现象 | 可能原因 | 优先优化 |
|---|---|---|
| 首次访问慢,后续快 | DNS 慢、未建立缓存、无 HTTP/2 | 换 DNS、启用 keepalive 与 HTTP/2 |
| 所有站点同时慢,SSH 也卡 | 整机资源耗尽(内存换页或磁盘打满) | 查 free、iostat、磁盘占用 |
| TTFB 高,静态页也慢 | PHP-FPM 池太小、Web 服务配置问题 | 查进程池状态、Nginx 错误日志 |
| 动态页慢,静态页快 | 数据库慢查询 | 开慢查询日志,用 EXPLAIN 分析 |
| 偶发性极慢 | 外部 API 调用、锁等待、定时任务抢资源 | 查应用日志、MySQL PROCESSLIST、crontab |
| 页面加载出来后图片慢慢刷 | 图片体积大、未压缩、无懒加载 | 压缩转 WebP、懒加载、CDN |
| 只有部分地区慢 | 区域网络链路、CDN 调度 | mtr 追踪、调整 CDN 配置 |
| 高峰期慢,平时正常 | 并发能力不足、连接池耗尽 | 扩容、加缓存、削峰 |
优化顺序建议
面对一堆待优化项,按下面的顺序做,性价比最高:
第一梯队(配置改动,几小时内完成,收益明显):启用 gzip 或 brotli 压缩;配置静态资源缓存头;启用 OPcache;打开 HTTP/2;压缩并转换图片格式;消除重定向链。
第二梯队(需要定位,一到数天,收益最大):开启数据库慢查询日志,用 pt-query-digest 找出 Top 查询,补索引、改写法;处理 N+1 查询;给热点数据加 Redis 缓存。
第三梯队(架构调整,周级):引入 CDN;静态资源与数据库分离;把外部 API 调用改为异步或加缓存;必要时升级硬件配置或拆分服务。
关键提醒:不要跳过测量直接优化。 先记下当前的关键指标(首页 TTFB、完整加载时间、慢查询条数),每做完一项优化就复测一次,这样才能知道哪项改动真正有效。很多时候直觉上以为的瓶颈(比如服务器配置不够)其实完全不是问题所在。
同时要建立长期监控:服务端响应时间、错误率、数据库慢查询数量、磁盘使用率这些基础指标,配上告警阈值。性能优化不是一次性工作,而是持续观察的过程。
常见问题
TTFB 多少算正常?
静态页面或启用缓存后在一两百毫秒内算不错;未缓存的动态页面在几百毫秒通常可接受。如果超过一秒,用户体验会有明显感知。这还要看网络位置,跨洲访问的物理延迟本身就有几百毫秒。
服务器配置不够是主要原因吗?
多数情况下不是。常见的慢站点瓶颈在未经优化的图片、未压缩的资源、缺失的索引和 N+1 查询上,升级配置只会掩盖问题并延缓它的爆发。先把压测量出来再做决定。
加了 CDN 就一定快吗?
对远距离用户通常有明显改善,但如果瓶颈在源站的动态页面生成(TTFB 高),CDN 无法解决——它缓存的是静态内容。动态内容要么做页面缓存,要么优化源站。
数据库索引是不是越多越好?
不是。索引会加快查询但减慢写入,还占用磁盘和内存。应该针对实际慢查询创建必要的索引,并定期检查是否有从未被使用的冗余索引。
怎么判断是前端慢还是后端慢?
看 curl 的 time_starttransfer。它小(比如 200 毫秒)但页面 Load 时间很长,就是前端问题;它本身就大(比如三秒),重点查后端。
慢查询日志开了会影响性能吗?
影响很小,通常可以忽略。long_query_time 设得太小导致日志量爆炸才是真正的风险,同时要注意给慢日志文件配置 logrotate 避免占满磁盘。
总结
- 排查的第一步是把"慢"量化并分段:用
curl -w分解 DNS、TCP、TLS、TTFB、传输各阶段,用浏览器 DevTools 看整体瀑布图,先确定是网络慢、服务端慢还是前端慢。 - Nginx 日志加
$request_time和$upstream_response_time两个字段是最有价值的长期投入:前者高后者低说明瓶颈在传输,两者都高说明瓶颈在后端应用或数据库。 - 服务器资源层看三件事:
si/so是否非零(内存换页)、%util是否持续满(磁盘打满、await高)、以及 load 与核心数的比值;SSH 也卡基本可以确定是整机资源问题。 - 数据库是动态站点最常见的瓶颈源,慢查询日志加
EXPLAIN加索引优化通常比升级硬件有效得多,注意 N+1 查询和大偏移量分页这两个隐形杀手。 - 前端问题集中在图片体积、懒加载、渲染阻塞脚本和第三方脚本上,这些优化不需要改业务逻辑,成本低收益高。
- 优化按"先看数据再动手"推进:配置类(压缩、缓存头、OPcache、HTTP/2)先做,数据类(慢查询、索引、缓存层)次之,架构类(CDN、分离部署、扩容)最后,每步都要复测验证效果。
企业QQ咨询




