做Web开发,性能优化是一个永恒的话题。

很多开发者,尤其是初学者,不重视性能优化。觉得功能能跑就行,性能差一点没关系。但实际上,性能直接影响用户体验和业务数据。

研究表明,页面加载时间每增加1秒,转化率就会下降7%;页面加载超过3秒,53%的移动用户会离开。性能差的网站,用户体验差,跳出率高,转化率低,甚至会影响SEO排名(Google已经把页面速度作为排名因素)。

而且,性能差的网站,服务器资源消耗大,需要更多的服务器才能支撑同样的流量,成本更高。性能优化好了,既能提升用户体验,又能节省服务器成本,一举两得。

PHP是一门解释型语言,性能本身就比编译型语言(如C、Java)差。如果代码写得不好,性能问题会更明显。但PHP的性能优化空间很大,通过合理的优化,可以让PHP应用的性能提升几倍甚至几十倍。

今天分享PHP性能优化的最佳实践,从代码、数据库、缓存、服务器等多个层面,帮你打造高性能的PHP应用。

性能优化的原则

在开始优化之前,先明确几个原则:

1. 先测量,再优化 不要凭感觉优化,要用数据说话。先用工具(如Xdebug、Blackfire、New Relic)测量性能瓶颈,找到最慢的地方,再有针对性地优化。不要浪费时间优化那些不影响整体性能的地方。

2. 避免过早优化 不要在项目一开始就过度优化,先把功能做出来,再根据实际情况优化。过早优化会增加开发复杂度,可能做无用功。

3. 优化瓶颈,而不是所有地方 根据二八定律,80%的性能问题来自20%的代码。找到那20%的瓶颈,重点优化,效果最好。不要试图优化所有代码,那样效率太低。

4. 优化后要验证 优化后,要用同样的工具测量,验证优化效果。不要想当然地认为优化了就一定更快,有时候优化反而可能更慢。

代码层面优化

1. 选择更快的PHP版本

PHP的每个版本,性能都有提升。PHP 7比PHP 5.6快了约2倍,PHP 7.4比PHP 7.0又快了不少。如果还在用PHP 5.x,升级到PHP 7.4+,性能就能提升很多。

2015年的时候,PHP 7还在开发中(2015年12月才发布PHP 7.0),主流是PHP 5.6。但如果是现在,强烈推荐用PHP 7.4+或PHP 8.x。

2. 用OPcache

OPcache是PHP的字节码缓存扩展,它把PHP脚本编译后的字节码缓存起来,避免每次请求都重新编译。开启OPcache后,PHP性能能提升50%-100%。

php.ini配置:

opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1

生产环境建议把opcache.validate_timestamps设为0,避免每次请求都检查文件修改时间,部署后手动重启PHP-FPM。

3. 避免在循环中做耗时操作

在循环中做数据库查询、文件操作、网络请求等耗时操作,会严重影响性能。应该把这些操作移到循环外,批量处理。

// 不好的:循环中查询数据库
foreach ($ids as $id) {
    $user = $pdo->query("SELECT * FROM users WHERE id = $id")->fetch();
    // ...
}

// 好的:批量查询
$idsStr = implode(',', array_map('intval', $ids));
$users = $pdo->query("SELECT * FROM users WHERE id IN ($idsStr)")->fetchAll();
foreach ($users as $user) {
    // ...
}

4. 用单引号代替双引号

PHP中,双引号字符串会解析变量和转义序列,单引号字符串不会。所以单引号比双引号稍微快一点。虽然差异很小,但在大量字符串处理时,累积起来还是有影响的。

// 稍微快一点
$str = 'Hello World';
$str2 = 'Hello, ' . $name;

// 稍微慢一点(会解析变量)
$str = "Hello World";
$str2 = "Hello, $name";

不过,这个优化效果很小,不要为了这个牺牲代码可读性。

5. 避免不必要的变量和函数调用

// 不好的:不必要的变量
$name = $_POST['name'];
echo $name;

// 好的:直接使用
echo $_POST['name'];

// 不好的:循环中重复调用函数
for ($i = 0; $i < count($array); $i++) {
    // ...
}

// 好的:提前计算
$count = count($array);
for ($i = 0; $i < $count; $i++) {
    // ...
}

6. 用isset()代替count()和strlen()判断

// 不好的
if (count($array) > 0) {}
if (strlen($str) > 0) {}

// 好的
if (!empty($array)) {}
if ($str !== '') {}
if (isset($array[0])) {}

7. 尽量用PHP内置函数

PHP内置函数是用C实现的,比自己写的PHP函数快很多。尽量用内置函数,不要自己重复实现。

// 不好的:自己写字符串长度
function my_strlen($str) {
    $len = 0;
    while (isset($str[$len])) $len++;
    return $len;
}

// 好的:用内置函数
strlen($str);

8. 关闭不必要的扩展

PHP加载的扩展越多,启动越慢,内存占用越大。只加载需要的扩展,关闭不需要的。

数据库层面优化

数据库往往是Web应用最大的性能瓶颈。数据库优化是性能优化的重中之重。

1. 加索引

索引是数据库性能优化最有效的手段。给经常用于WHERE、JOIN、ORDER BY、GROUP BY的字段加索引,能大大提升查询速度。

-- 加索引
ALTER TABLE posts ADD INDEX idx_category_id (category_id);
ALTER TABLE posts ADD INDEX idx_created_at (created_at);
ALTER TABLE posts ADD INDEX idx_slug (slug);

-- 复合索引
ALTER TABLE posts ADD INDEX idx_category_status (category_id, status);

注意:索引不是越多越好,索引会降低写入速度(INSERT/UPDATE/DELETE),占用更多磁盘空间。只给需要的字段加索引。

2. 优化查询语句

  • 用具体字段代替SELECT *,只查询需要的字段
  • 用LIMIT限制返回行数,避免查询大量数据
  • 用EXPLAIN分析查询执行计划,看是否用到索引
  • 避免在WHERE子句中对字段使用函数或运算,会导致索引失效
  • 用JOIN代替子查询,JOIN通常比子查询快
-- 不好的
SELECT * FROM posts WHERE YEAR(created_at) = 2015;

-- 好的(字段上不用函数)
SELECT id, title, created_at FROM posts WHERE created_at >= '2015-01-01' AND created_at < '2016-01-01';

3. 分页优化

传统的LIMIT offset, count分页,offset越大越慢。大数据量分页用"上一页/下一页"或"游标分页"。

-- 传统分页(offset大了慢)
SELECT * FROM posts ORDER BY id LIMIT 10000, 10;

-- 游标分页(快)
SELECT * FROM posts WHERE id < 10000 ORDER BY id DESC LIMIT 10;

4. 数据库配置优化

  • innodbbufferpool_size:InnoDB缓冲池大小,建议设为物理内存的50%-70%
  • innodblogfile_size:日志文件大小,建议256M-1G
  • querycachetype:查询缓存(MySQL 8.0已移除,不推荐用)
  • max_connections:最大连接数,根据实际情况设置
  • 用InnoDB引擎,支持事务和行级锁,性能更好

缓存层面优化

缓存是提升性能最有效的手段之一。通过缓存,把频繁访问的数据存到内存中,避免每次都查数据库。

1. Redis缓存

Redis是高性能的内存数据库,非常适合做缓存。

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

// 读缓存
$key = 'post:' . $id;
$data = $redis->get($key);
if ($data === false) {
    // 缓存未命中,查数据库
    $data = $pdo->query("SELECT * FROM posts WHERE id = $id")->fetch();
    // 写入缓存,过期时间1小时
    $redis->setex($key, 3600, serialize($data));
} else {
    $data = unserialize($data);
}

缓存策略:

  • 页面缓存:把整个页面的HTML缓存起来
  • 数据缓存:把数据库查询结果缓存起来
  • 对象缓存:把对象序列化后缓存
  • 片段缓存:把页面的一部分缓存起来

注意:

  • 设置合理的过期时间,避免数据过期
  • 数据更新时,主动删除或更新缓存
  • 防止缓存穿透(查询不存在的数据,缓存空值)
  • 防止缓存雪崩(大量缓存同时过期,加随机过期时间)
  • 防止缓存击穿(热点key过期,加互斥锁)

2. 浏览器缓存

通过HTTP头,让浏览器缓存静态资源(CSS、JS、图片),减少重复请求。

Nginx配置:

location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
}

HTML页面可以设置较短的缓存时间,或用ETag/Last-Modified做协商缓存。

3. CDN加速

把静态资源放到CDN上,用户从最近的CDN节点获取资源,速度更快。国内推荐用阿里云CDN、腾讯云CDN、七牛云等。

服务器层面优化

1. 用PHP-FPM + Nginx

PHP-FPM是PHP的FastCGI进程管理器,性能好,稳定。Nginx是高性能的Web服务器,处理静态资源快,反向代理强。Nginx + PHP-FPM是目前PHP生产环境的标准组合。

2. PHP-FPM配置优化

[www]
user = www
group = www
listen = 127.0.0.1:9000
pm = dynamic
pm.max_children = 50        ; 最大子进程数
pm.start_servers = 5        ; 启动时进程数
pm.min_spare_servers = 2    ; 最小空闲进程数
pm.max_spare_servers = 8    ; 最大空闲进程数
pm.max_requests = 500       ; 每个进程处理多少请求后重启,防止内存泄漏

pm.max_children根据服务器内存设置,每个PHP-FPM进程约占20-50M内存。比如2G内存的服务器,设为30-50比较合适。

3. Nginx配置优化

worker_processes auto;          ; CPU核心数
worker_connections 10240;       ; 每个进程最大连接数
sendfile on;                    ; 高效文件传输
tcp_nopush on;                  ; 减少网络报文数量
tcp_nodelay on;                 ; 禁止Nagle算法
keepalive_timeout 65;           ; 长连接超时
gzip on;                        ; 开启Gzip压缩
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1024;           ; 最小压缩长度
client_max_body_size 20m;       ; 最大上传大小

4. 开启Gzip压缩

Gzip压缩能把文本类资源(HTML、CSS、JS、JSON)压缩到原来的30%-50%,大大减少传输数据量,提升加载速度。

5. 用HTTP/2

HTTP/2支持多路复用、头部压缩、服务器推送,比HTTP/1.1快很多。Nginx 1.9.5+支持HTTP/2,需要HTTPS。

前端层面优化

前端优化也很重要,很多性能问题在前端。

1. 减少HTTP请求

  • 合并CSS和JS文件
  • 用CSS Sprite合并小图标
  • 用Base64内联小图片
  • 减少页面中的外部资源

2. 资源压缩和优化

  • 压缩CSS和JS(去掉空格、注释,缩短变量名)
  • 压缩图片(用TinyPNG等工具,WebP格式比JPG小30%)
  • 开启Gzip/Brotli压缩

3. 资源加载优化

  • CSS放在head中,JS放在body末尾
  • 用async/defer异步加载JS
  • 懒加载图片(滚动到可视区域再加载)
  • 预加载关键资源

4. 用CDN

静态资源放CDN,加速访问。

其他优化技巧

1. 异步处理

耗时操作(如发送邮件、生成报表、推送通知)用异步队列处理,不要在请求中同步执行,避免阻塞。

可以用Redis队列、RabbitMQ、Beanstalkd等消息队列。

2. 静态化

对于不经常变化的页面(如文章详情页),可以生成静态HTML文件,直接由Nginx提供,不需要PHP处理,性能极高。

3. 读写分离

数据库读写分离,读操作走从库,写操作走主库,提升读性能。

4. 分库分表

数据量很大时,分库分表,提升查询和写入性能。

性能监控和测量

优化的前提是测量。推荐工具:

  • Xdebug:PHP调试和性能分析工具,生成cachegrind文件,用KCacheGrind/WinCacheGrind分析
  • Blackfire:SensioLabs出品的PHP性能分析工具,可视化好,易用
  • New Relic:APM工具,监控应用性能,找到慢事务、慢SQL
  • MySQL慢查询日志:记录执行慢的SQL语句,分析优化
  • ab/wrk:压力测试工具,测试网站并发性能

总结

PHP性能优化是一个系统工程,涉及多个层面:

  1. 代码层面:用最新PHP版本、开启OPcache、优化循环、用内置函数
  2. 数据库层面:加索引、优化查询、分页优化、配置优化
  3. 缓存层面:Redis缓存、浏览器缓存、CDN加速
  4. 服务器层面:Nginx+PHP-FPM、配置优化、Gzip压缩、HTTP/2
  5. 前端层面:减少HTTP请求、压缩资源、优化加载、CDN
  6. 架构层面:异步处理、静态化、读写分离、分库分表

性能优化的原则:先测量再优化、优化瓶颈、优化后验证。

性能优化没有终点,需要持续监控、持续优化。但只要抓住主要矛盾,从最影响性能的地方入手,就能用最小的代价获得最大的性能提升。

希望这篇文章能帮你打造高性能的PHP应用。