Laravel性能用缓存和索引吗

wen PHP项目 34

本文目录导读:

Laravel性能用缓存和索引吗

  1. 数据库索引:查询性能的基石
  2. 缓存:消灭重复劳动
  3. 组合策略:索引 + 缓存实战
  4. 其他关键性能优化点

是的,缓存和索引是提升 Laravel 应用性能最核心、最有效的两种策略,它们分别解决了不同层面的瓶颈:

  • 索引:主要解决数据库查询慢的问题(I/O 瓶颈)。
  • 缓存:主要解决重复计算/重复查询的问题(CPU 和 I/O 瓶颈)。

下面我详细拆解这两者在 Laravel 中的具体实践和配置方法。


数据库索引:查询性能的基石

没有合适的索引,哪怕只有 10 万条数据,一个简单的 WHERE 查询也可能让你等上好几秒。

什么时候必须加索引?

  • WHERE 子句WHERE user_id = ? 中的 user_id
  • JOIN 的表连接字段ON orders.user_id = users.id 中的 user_id
  • ORDER BY 字段ORDER BY created_at 中的 created_at
  • GROUP BY 字段
  • 唯一性约束:邮箱、手机号等。

Laravel 迁移中的索引实践

在迁移文件里,不要只盯着字段类型,索引声明同样重要

// database/migrations/xxxx_create_orders_table.php
Schema::create('orders', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('user_id');
    $table->string('order_no')->unique(); // 唯一索引,快速查找订单号
    $table->string('status')->index(); // 普通索引,按状态筛选
    $table->timestamp('created_at')->index(); // 排序或按时间范围查询
    $table->foreignId('payment_method_id')->constrained(); // 外键自动创建索引
    // 联合索引:经常同时按 user_id 和 status 查询时
    $table->index(['user_id', 'status']);
});

避免索引失效的常见坑

  • 不要在索引列上使用函数WHERE DATE(created_at) = '2024-01-01'(失效)。
    • 正确做法WHERE created_at >= '2024-01-01' AND created_at < '2024-01-02'
  • 避免 LIKE 前置通配符WHERE name LIKE '%张三%'(失效,除非用全文索引)。
    • 部分场景可考虑WHERE name LIKE '张三%'(有效)。
  • 类型不一致user_id 是 int 类型,where('user_id', '1') 是字符串 '1',可能导致隐式类型转换(失效)。
    • 始终使用正确的类型where('user_id', 1)

如何检查索引效果?

在 Laravel Tinker 或 MySQL 客户端中,对你怀疑慢的查询前面加上 EXPLAIN

EXPLAIN SELECT * FROM orders WHERE user_id = 123;

关注 typerows 字段:

  • type = ALL:全表扫描,必须加索引
  • type = refrange:使用了索引。
  • rows 数字越小越好。

缓存:消灭重复劳动

索引只能加速 SQL,但无法解决“同一个页面被 1000 个人访问,就查 1000 次数据库”的问题,缓存就是干这个的。

Laravel 的缓存门面(Facade)

Laravel 提供了统一的 Cache 门面,可以无缝切换文件、Redis、Memcached 等驱动。生产环境强烈建议用 Redis 或 Memcached

use Illuminate\Support\Facades\Cache;
// 1. 从缓存中获取数据,如果没有则通过闭包计算并存入缓存
$users = Cache::remember('active_users', 3600, function () {
    return User::where('is_active', 1)->orderBy('name')->get();
});
// 这里 3600 秒 = 1 小时内,相同的请求只会查一次数据库
// 2. 手动存储
Cache::put('key', 'value', $seconds);
// 3. 检查是否存在
if (Cache::has('key')) {
    //
}

缓存常用场景

  • 全页面缓存:对整个响应进行缓存(适合内容基本不变的页面)。
  • 片段缓存
    • 配置数据Setting::first()(网站全局设置)。
    • 状态列表:省份、城市、文章分类等枚举型数据。
    • 热门排行榜:文章阅读量 Top 10,每小时更新一次。
    • 用户 Token / Session:使用 Redis 避免文件 Session 的性能问题。

缓存失效策略(非常重要)

缓存最大的敌人是“数据不一致”。

  • 时间过期:设置合理的 TTL(生存时间),如上例的 3600 秒。
  • 主动失效(标签/手动清除):当数据被修改时,必须立即删除或更新缓存

示例:更新用户信息时清理缓存

public function update(Request $request, User $user)
{
    $user->update($request->validated());
    // 关键步骤:删除与该用户相关的缓存
    // 假设你在其他地方用 'user_'.$user->id 缓存了用户数据
    Cache::forget('user_'.$user->id);
    // 或者使用缓存标签(如果驱动支持,如 Redis/Memcached)
    Cache::tags(['users', 'profiles'])->flush();
    return redirect()->back()->with('success', '更新成功');
}

查询结果缓存(神器:Remember

除了显式调用 Cache,你还可以在模型上直接链式调用 remember()

$users = User::where('account_type', 'vip')
             ->remember(1440) // 缓存 1440 分钟(24小时)
             ->get();

注意remember() 在多次查询不同参数时会生成不同的缓存键,但需要谨慎使用,确保数据变化时你能手动清除对应键。


组合策略:索引 + 缓存实战

假设你有个电商首页,显示“热销商品 Top 10”:

  1. 第一步(索引):确保 products 表的 sales_count 字段有索引。
  2. 第二步(缓存):把查询结果缓存起来,设置 5 分钟过期。
// app/Http/Controllers/HomeController.php
public function index()
{
    $hotProducts = Cache::remember('hot_products', 300, function () {
        // 有了索引,这个查询本身很快
        // 但缓存让它更快,且不再消耗数据库连接
        return Product::where('status', 'on_sale')
                      ->orderBy('sales_count', 'desc')
                      ->take(10)
                      ->get();
    });
    return view('home', compact('hotProducts'));
}

当一个商品被购买(sales_count 变化)时:

// 在 OrderController 的 placeOrder 方法或 ProductObserver 的 updated 事件中
Cache::forget('hot_products'); // 清除热销排行缓存

其他关键性能优化点

  1. 配置缓存(生产环境必做)

    php artisan config:cache
    php artisan route:cache
    php artisan view:cache

    这会把配置文件合并为单一数组,路由也转为静态数组,大幅减少文件 I/O。

  2. 队列处理耗时任务:发邮件、生成 PDF、处理图片等放到队列里。

  3. Opcache(PHP 扩展):开启 Opcache 可以避免每次请求都重新编译 PHP 文件。

  4. 静态资源优化:合并 CSS/JS、开启 Gzip、使用 CDN。


策略 解决的核心问题 典型工具/做法
数据库索引 查询速度慢(扫描行数多) ->index() 迁移、EXPLAIN 分析
缓存 重复计算/重复查询(高并发场景) Cache::remember()、Redis、Cache::forget()
两者结合 先让每个查询快起来,再让高频查询变零次查询 见上面的“热销商品”例子

一句话建议先确保每个慢查询都有合适的索引,再用缓存把它变成一个“不发生查询”的请求。 两者缺一不可,先用索引打好基础,再用缓存削平峰值。

抱歉,评论功能暂时关闭!