拒絕手寫 SQL 地獄!2026 Laravel Eloquent ORM 終極指南:從 N+1 問題到高效能查詢的優雅煉金術

2026/02/24 | Laravel技術分享, 全端與程式開發, 架構與效能優化

拒絕手寫 SQL 地獄!2026 Laravel Eloquent ORM 終極指南:從 N+1 問題到高效能查詢的優雅煉金術

嗨,我是 Eric,浪花科技的資深工程師。如果你在 2026 年還在 PHP 裡面瘋狂手寫 SELECT * FROM,然後用字串串接來處理關聯資料,那你的鍵盤可能已經在哭泣了。Laravel 的 Eloquent ORM 不僅僅是一個資料庫抽象層,它是讓你的程式碼從「義大利麵」進化成「米其林擺盤」的關鍵。今天這篇不講廢話,直接帶你深入 Eloquent 的核心,從基礎用法到資深工程師才會注意的效能陷阱,一次講清楚。

什麼是 Eloquent ORM?為什麼 2026 年我們還在用?

Eloquent 是 Laravel 內建的 ORM (Object-Relational Mapper),它實作了 Active Record 模式。簡單來說,它讓資料庫中的每一個「資料表 (Table)」都有一個對應的「模型 (Model)」類別。你不用寫 SQL 指令,而是像操作物件一樣操作資料庫。

雖然到了 2026 年,甚至出現了 Google Antigravity 這種 AI 輔助開發工具,但理解底層邏輯依然重要。AI 可以幫你寫 Code,但它不一定知道你的查詢會不會把伺服器記憶體吃光。Eloquent 的語法糖 (Syntactic Sugar) 非常甜,但如果吃太多(例如濫用查詢),也是會得糖尿病(效能低落)的。

1. 定義模型與基礎 CRUD:優雅的起手式

在 Laravel 12 或更高版本中,定義一個模型依然直觀。假設我們有一個 posts 資料表,對應的 Model 如下:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    // 指定資料表名稱 (如果是慣例命名可省略)
    protected $table = 'posts';

    // 允許被批量賦值的欄位 (Mass Assignment)
    protected $fillable = ['title', 'content', 'user_id', 'is_published'];

    // 預設轉換型別 (Casting),2026 年必備的型別安全
    protected $casts = [
        'is_published' => 'boolean',
        'published_at' => 'datetime',
    ];
}

有了這個 Model,你的 CRUD (新增、讀取、更新、刪除) 操作就會變得異常優雅:

  • 建立: Post::create(['title' => 'Laravel ORM 教學', ...]);
  • 讀取: $posts = Post::where('is_published', true)->orderBy('created_at', 'desc')->get();
  • 更新: $post->update(['title' => '新標題']);
  • 刪除: $post->delete();

這裡有個工程師的小囉嗦:永遠要設定 $fillable$guarded。在 2026 年,資安意識已經是基本功,Mass Assignment 漏洞如果還出現,真的會被笑掉大牙。

2. 關聯關係 (Relationships):ORM 的靈魂

Eloquent 最強大的地方在於處理資料表之間的關聯。別再手寫 JOIN 了,除非你在做極端效能優化的報表。

一對多 (One To Many)

一個使用者有多篇文章:

// User Model
public function posts()
{
    return $this->hasMany(Post::class);
}

// Post Model
public function user()
{
    return $this->belongsTo(User::class);
}

多對多 (Many To Many)

文章與標籤 (Tags) 的關係:

// Post Model
public function tags()
{
    return $this->belongsToMany(Tag::class);
}

使用起來就像存取屬性一樣自然:$user->posts 會回傳該使用者的所有文章集合 (Collection)。

3. 效能殺手:N+1 問題與 Eager Loading

這是面試資深工程師的必考題,也是導致網站變慢的元兇之一。N+1 問題發生在你迴圈遍歷模型時,對每個模型都執行一次關聯查詢。

❌ 錯誤示範 (Lazy Loading):

$users = User::all(); // 1 次查詢

foreach ($users as $user) {
    echo $user->posts->count(); // 執行 N 次查詢 (每個 User 查一次)
}
// 總共查詢次數:1 + N

如果這頁有 100 個使用者,你就對資料庫發送了 101 次查詢。在流量高峰時,資料庫會直接掛給你看。

✅ 正確示範 (Eager Loading):

使用 with() 方法預先載入關聯資料。

$users = User::with('posts')->get(); // 總共只執行 2 次查詢!

foreach ($users as $user) {
    echo $user->posts->count(); // 不會再查詢資料庫
}

在 Laravel 10 之後,我們甚至可以在開發環境開啟 Strict Mode,強制禁止 Lazy Loading,這是我強烈建議在 AppServiceProvider 中設定的:

Model::preventLazyLoading(!app()->isProduction());

4. 進階技巧:Scopes 與 Mutators

為了讓程式碼更乾淨 (Clean Code),不要把複雜的 where 條件散落在 Controller 各處。

Scopes (查詢範圍)

在 Model 中定義常用的查詢邏輯:

// Post Model
public function scopePublished($query)
{
    return $query->where('is_published', true)->whereNotNull('published_at');
}

// 使用時
$posts = Post::published()->get();

Accessors & Mutators (存取器與修改器)

在 Laravel 新版本中,我們使用 Attribute 類別來定義,這比舊語法更簡潔:

use Illuminate\Database\Eloquent\Casts\Attribute;

protected function title(): Attribute
{
    return Attribute::make(
        get: fn (string $value) => ucfirst($value), // 取出時自動大寫首字
        set: fn (string $value) => strtolower($value), // 存入時自動轉小寫
    );
}

5. 何時該拋棄 Eloquent 回歸 SQL?

雖然 Eloquent 很棒,但在 2026 年的大數據場景下,它也不是萬能的。Eloquent 在處理「大量資料匯入」或「複雜報表統計」時,因為要實例化大量物件,記憶體消耗會很大。

  • 大量更新: 使用 Post::where(...)->update([...]) 直接轉成 SQL 執行,不要撈出來迴圈 update。
  • 大數據讀取: 使用 chunk()cursor() 來分批處理,避免一次把 10 萬筆資料塞爆 RAM。
  • 複雜報表: 如果涉及多層 Group By 和複雜運算,直接寫 Raw SQL 或使用 Query Builder 效能會好得多。

結論

Laravel Eloquent ORM 是現代 PHP 開發者的神兵利器。它讓我們能專注於業務邏輯,而不是 SQL 語法。但就像所有的強大工具一樣,你需要理解它的運作原理才能駕馭它。記住:善用 Eager Loading、封裝 Scope、並在必要時果斷切換回 Query Builder,這就是資深工程師的生存之道。

推薦閱讀

想更深入了解 Laravel 架構與效能優化嗎?這幾篇是你的必讀清單:

你的專案資料庫查詢慢到懷疑人生嗎?或者你需要客製化開發高流量的 Laravel 系統?歡迎聯繫浪花科技,讓我們幫你把系統體質調整到最佳狀態。

需要專業的 Laravel 開發與效能優化協助?

別讓技術債拖垮你的業務成長。立即聯繫 Eric 與浪花科技團隊!

填寫表單聯繫我們

常見問題 (FAQ)

Q1: Eloquent ORM 一定比寫 SQL 慢嗎?

在單純的查詢執行時間上,Eloquent 因為需要將資料轉換成物件 (Hydration),確實比原生 SQL 或 Query Builder 稍慢一點點。但在 95% 的 web 應用場景中,這個差異是微乎其微的(毫秒級別)。通常導致慢的主因是寫法不當(如 N+1 問題)或資料庫索引沒設好,而不是 ORM 本身。

Q2: 什麼時候應該使用 `save()` 什麼時候用 `create()`?

`create()` 是批量賦值的方法,需要設定 `$fillable`,適合直接從表單接收陣列資料建立模型。`save()` 則是用於建立新實例後逐一設定屬性,或者更新現有模型。兩者底層都是執行 INSERT 或 UPDATE,視使用情境而定。

Q3: Laravel 的 `with()` 和 `load()` 有什麼不同?

`with()` 是在查詢建構時使用(Eager Loading),它會立即將關聯資料撈出來。`load()` 則是在模型已經被撈出來之後使用(Lazy Eager Loading),如果你已經有一個 User 物件,後來才發現需要用到他的 Posts,這時就可以用 `$user->load(‘posts’)`。