Laravel 效能優化筆記

提升 Laravel 應用程式效能是一個持續的過程,需根據應用特性選擇合適的優化策略,並持續監控與調整,以確保高負載下仍能維持良好效能。


一、批次處理:chunk()chunkById()

chunk()

Model::chunk($count, function ($records) {
    // 處理每批資料
});

優點:

  • 節省記憶體,每次只載入少量資料。
  • 適合長時間處理的大型資料集。

缺點:

  • 查詢次數增加,速度可能較慢。
  • 程式邏輯較複雜,需自行處理狀態保存。
use App\Models\Post;

Post::chunk(100, function ($posts) {
    foreach ($posts as $post) {
        echo $post->title . "\n";
    }
});

// SQL: select * from posts order by id asc limit 100 offset 0 / 100 / 200

chunkById()

use App\Models\Order;

Order::where('status', '>', 0)->chunkById(500, function ($orders) {
    foreach ($orders as $order) {
        // 處理每筆訂單
    }
}, 'order_id'); // 避免預設 order by id 導致索引失效

適用時機:

  • 資料需讀取後更新再寫回資料庫。
  • 可避免使用 offset 導致的效率低落與資料遺漏問題。

注意事項:

  • 使用 chunkById 時若未指定欄位,預設使用主鍵排序,可能導致索引失效。
  • 使用 chunkById 時 請勿使用排序(orderBy),以免影響邏輯正確性。

二、使用 cursor() 減少記憶體使用

foreach (User::where('active', true)->cursor() as $user) {
    echo $user->name . "\n";
}

優點:

  • 每次僅讀取一筆資料,顯著降低記憶體消耗。

缺點:

  • 不能直接更新資料(資料僅於迭代期間存於記憶體)。
  • 每次迭代都會進行資料庫讀取,速度可能較慢。

三、只查需要的欄位:使用 select() 替代 all()

User::select('id', 'name')->get();

優點:

  • 減少資料傳輸與記憶體使用。
  • 提高查詢效率,尤其適用於大表。

四、避免使用 toArray() 若需保留模型功能

$collection = User::all();
$array = $collection->toArray(); // 失去模型功能

建議:
除非要進行 JSON 序列化或與外部介面整合,否則保留 Collection 結構可使用更多模型相關操作與方法。


五、使用 pluck() 只取得必要欄位值

User::where('age', 30)->pluck('age', 'name');
// 回傳格式: ['Alice' => 30, 'Carol' => 30]

優點:

  • 僅回傳特定欄位,大幅減少資料量與查詢負擔。
  • 寫法簡潔、意圖明確。

六、使用 with() 避免 N+1 查詢問題

// 不推薦:N+1 問題
$users = User::all();
foreach ($users as $user) {
    $user->posts; // 每次觸發額外查詢
}

// 推薦:使用 with() 預加載
$users = User::with('posts')->get();
foreach ($users as $user) {
    foreach ($user->posts as $post) {
        echo "User: {$user->name}, Post: {$post->title}\n";
    }
}

七、使用 paginate() 分頁查詢

$users = User::paginate(10);

foreach ($users as $user) {
    echo $user->name . "\n";
}

echo $users->links(); // 顯示分頁導航

進階用法:

$users = User::paginate(10, ['id', 'name'], 'page', 2);

chunk() 差異:

  • paginate() 用於前端分頁顯示。
  • chunk() 用於後端批次處理大量資料,不提供分頁導覽。

八、提升記憶體效能與資料讀取速度

1. 使用緩存(Cache)

  • 使用 Redis 或 Memcached 儲存頻繁查詢的資料。
  • Laravel Cache 支援資料快取、設定快取等多種應用。

2. 使用資料庫索引(Index)

  • 對常查詢欄位加索引可顯著提升查詢速度。
  • 注意:過多索引會增加寫入成本與記憶體佔用。

3. 資料分區(Partitioning)

  • 水平分區:依時間、區域、用戶等維度分表。
  • 垂直分區:將常用欄位與次要欄位拆分至不同表。

4. 延遲與預加載的取捨

  • Lazy Loading:初始不載入關聯資料,節省記憶體。
  • Eager Loading:事先載入資料,減少資料庫查詢次數。
// Lazy
$users = User::all();
foreach ($users as $user) {
    $user->posts;
}

// Eager
$users = User::with('posts')->get();

5. 查詢優化與分析

  • 使用 EXPLAIN 分析 SQL 查詢計畫。
  • 使用適當的索引、避免過多 join 與子查詢。

6. 後台任務處理(Queue / Jobs)

  • 大量資料轉換或寫入建議放至 Laravel 任務隊列中執行。
  • 可降低即時應用的記憶體與 CPU 壓力。

備註:
效能優化無銀彈,應根據應用場景進行監控與漸進調整。推薦搭配如 Laravel Telescope、Debugbar、New Relic 等工具分析瓶頸來源。