PHP 性能优化实战:从 3s 到 300ms,我是怎么优化一个接口的?

PHP 性能优化实战:从 3s 到 300ms,我是怎么优化一个接口的?

一次真实的接口优化记录,不谈虚的,只讲实战。

背景:问题从哪来?

这是一个典型的后台列表接口:

  • 技术栈:PHP 8.1 + Laravel 10 + MySQL 8
  • 场景:管理后台订单列表
  • 数据量:订单表 200w+,关联用户表、商品表
  • 原始表现:
    • 平均响应:3s+
    • QPS:不到 10
    • 数据库 CPU 经常飙高

接口逻辑很简单:分页查询订单 + 关联用户信息 + 商品信息 + 简单统计。

但"简单"背后,藏着一堆性能坑。


Step 1:先定位,再优化(别瞎猜)

优化第一条原则:先 profiling,再动手。

工具组合

  • Xdebug + CacheGrind(本地分析)
  • Laravel Debugbar(SQL / 内存)
  • MySQL EXPLAIN(索引问题)

初步结论

Debugbar 一眼看出问题:

  • SQL 执行时间:2.5s+
  • SQL 条数:N+1 问题严重
  • 内存峰值:120MB+
  • 重复查询同一个用户 / 商品数据

结论:90% 的问题在数据库。


Step 2:干掉 N+1,这是最大头

原代码(问题版)

复制代码
$orders = Order::paginate(20);

foreach ($orders as $order) {
    $order->user->name;
    $order->product->title;
}

Laravel Debugbar 显示:

  • 1 条查订单
  • 20 条查用户
  • 20 条查商品
  • 合计 41 条 SQL

优化后(Eager Loading)

复制代码
$orders = Order::with(['user', 'product'])
    ->paginate(20);

结果:

  • SQL 数量:3 条
  • SQL 时间:从 2.5s → 600ms

✅ 一次简单的 with(),性能直接提升 4 倍。


Step 3:索引不是万能,但没索引是万万不能

原查询

复制代码
select * from orders
where status = 1
and created_at >= '2024-01-01'
order by created_at desc
limit 20 offset 0;

EXPLAIN 一看:

  • type: ALL
  • rows: 2,000,000
  • Using filesort: ✅

加复合索引

复制代码
ALTER TABLE orders
ADD INDEX idx_status_created_at (status, created_at);

再次 EXPLAIN:

  • type: range
  • rows: 20
  • Using filesort: ❌

效果:

  • SQL 时间:600ms → 150ms

✅ 索引是性价比最高的优化手段。


Step 4:分页的 OFFSET 陷阱

当 offset 很大时,性能会急剧下降。

复制代码
limit 20 offset 100000;

MySQL 依然要扫描前 10 万行。

优化方案:游标分页(Cursor Pagination)

复制代码
$orders = Order::with(['user', 'product'])
    ->where('status', 1)
    ->cursorPaginate(20);

原理:

  • 不再使用 offset
  • 使用 where id > last_id

效果:

  • 深度分页响应:从 1s+ → 稳定 100ms 内

✅ 后台列表,99% 场景不需要"跳页",用游标分页刚刚好。


Step 5:减少不必要的数据传输

问题

  • select *
  • JSON 返回大量无用字段
  • 接口返回 200KB+

优化

复制代码
Order::select(['id', 'order_no', 'user_id', 'product_id', 'status', 'created_at'])

API Resource 精简字段:

复制代码
public function toArray($request)
{
    return [
        'id' => $this->id,
        'order_no' => $this->order_no,
        'status' => $this->status,
        'user' => [
            'id' => $this->user->id,
            'name' => $this->user->name,
        ],
        'product' => [
            'id' => $this->product->id,
            'title' => $this->product->title,
        ],
    ];
}

效果:

  • 响应体积:200KB → 30KB
  • 网络耗时明显下降

Step 6:缓存,但别乱缓存

适合缓存的

  • 商品信息(变化少)
  • 统计数据(非实时)

实现方式

复制代码
$product = Cache::remember(
    "product:{$order->product_id}",
    600,
    fn () => Product::find($order->product_id)
);

注意点

  • 缓存 key 要清晰
  • 更新数据时清缓存
  • 不缓存分页结果(容易脏数据)

效果:

  • 接口时间:150ms → 80ms

Step 7:PHP 层面的"小刀割肉"

1. 关闭 debug 模式

复制代码
APP_DEBUG=false
  • 减少日志
  • 关闭 Debugbar
  • 响应时间减少 20~30ms

2. 开启 OPcache(生产环境必开)

复制代码
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000

效果:

  • PHP 执行时间下降 30%+

3. 避免循环内做"重活"

❌ 错误示例:

复制代码
foreach ($orders as $order) {
    User::find($order->user_id);
}

✅ 正确方式:

  • 批量查询
  • 数组映射

Step 8:最终效果对比

指标 优化前 优化后
平均响应 3000ms 300ms​
SQL 数量 40+ 3​
内存峰值 120MB 35MB​
QPS <10 **80+**​

10 倍性能提升,不是靠"黑科技",而是靠一个个小点堆出来的。


总结:接口优化的"套路"

我总结了一个 PHP 接口优化 checklist,以后照着用:

  1. ✅ 先 profiling,再动手
  2. ✅ 干掉 N+1(Eager Loading)
  3. ✅ 检查索引(EXPLAIN)
  4. ✅ 避免大 OFFSET 分页
  5. ✅ 只查需要的字段
  6. ✅ 合理使用缓存
  7. ✅ 生产环境开 OPcache
  8. ✅ 精简 JSON 返回结构
  9. ✅ 减少循环内的 DB / IO 操作
  10. ✅ 用 Cursor Pagination 替代传统分页

写在最后

性能优化不是"炫技",而是用最小的成本解决最痛的问题。

相关推荐
传奇开心果编程1 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程1 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程2 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓2 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程2 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer
supabc1232 小时前
Celium:连接 Windows、Mac、Linux 与 Android,让远程访问和设备管理更简单
android·linux·windows·macos·远程访问·网络管理·celium
Dovis(誓平步青云)3 小时前
导览音频切换太快,旧讲解不能覆盖新展品
android·前端·javascript·ecmascript·音视频·宠物
五彩小白19 小时前
自回归和上下文
android
事圆则缓21 小时前
Android 多渠道打包实战:Product Flavors、签名、AAB 与发包校验
android
树下困觉眯1 天前
View绘制流程学习--获取WindowManager的三种方式
android·系统架构·view design