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 替代传统分页

写在最后

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

相关推荐
Dovis(誓平步青云)1 小时前
番茄钟真正难的不是倒计时:启动、暂停、重置与页面销毁
android·服务器·前端
mengge.cloud2 小时前
0824LAMP项目实战:部署WordPress博客平台小白教程
android·linux·运维·服务器·学习·nginx
YF02112 小时前
开机自启动预置App最简单、最快方案
android
2501_915918413 小时前
Rust 程序抓包解密,rustls 不认系统证书的几种办法
开发语言·后端·网络协议·ios·adb·https·rust
恋猫de小郭3 小时前
Flutter iOS 的深度优化 PR,搞笑的是贡献者被 Gemini 评审折磨
android·前端·flutter
撩得Android一次心动4 小时前
Kotlin 语言【知识点整理2】
android·开发语言·kotlin
Free飝Fly4 小时前
Android13及更新版本开启user版本adb root remount
adb
福大大架构师每日一题4 小时前
webrtc-rs/webrtc v0.20.3更新:Android 网络切换后 ICE Restart 卡死约 10 秒的问题终于解决
android·网络·webrtc
mmsx4 小时前
基于 Android 的本地商城订单 App:SQLite + 多角色架构的技术实践(有源码和文档)
android·架构·sqlite