SaaS 生存戰:Laravel 多租戶架構的資料庫抉擇與實戰攻略
SaaS 創業家,您正面臨技術的關鍵抉擇!本指南深入拆解 Laravel 多租戶架構的兩大核心:快速但高風險的「單一資料庫」雅房模式,以及安全隔離但維護複雜的「獨立資料庫」套房模式。我們提供 Global Scopes 和動態連線切換的實戰程式碼,助您避開 Redis 衝突與 Queue 陷阱。資料外洩或 Migration 地獄,您選擇哪一個?立即停止盲目開發,做出最符合您業務需求的架構決策,並諮詢專業建議,確保您的技術藍圖萬無一失!
SaaS 創業的技術賭注:Laravel 多租戶 (Multi-tenant) 系統設計實戰
嗨,我是浪花科技的資深工程師 Eric。最近又有新創團隊跑來問我:「Eric,我們想要做一個像 Shopify 那樣的 SaaS 平台,用 Laravel 開發,但資料庫該怎麼設計?是所有客戶塞在同一個資料庫,還是幫每個客戶開一個資料庫?」
老實說,這個問題在工程界就像「粽子該吃南部粽還是北部粽」一樣,永遠有兩派人馬在戰。但在 SaaS (Software as a Service) 的世界裡,這不只是口味問題,而是攸關你未來兩年會不會因為維護資料庫而過勞死的「生存問題」。
今天這篇文章,我不談太虛幻的理論,我們直接從 Laravel 的程式碼實作角度,來拆解 多租戶系統 (Multi-tenant System) 的架構選擇。無論你是剛起步的獨立開發者,還是準備擴張的技術長,這篇應該都能幫你省下不少走冤枉路的時間。
什麼是多租戶 (Multi-tenancy)?
簡單來說,就是「一套程式碼,服務多個客戶」。
想像你開了一棟公寓(伺服器與程式碼),每個房客(租戶/Tenant)都有自己的房間(資料)。
- 單一資料庫 (Single Database): 像是雅房,大家共用客廳衛浴(資料表),只是門上有貼名字(
tenant_id)。 - 獨立資料庫 (Database per Tenant): 像是獨立套房,每個人有自己的衛浴廚房(獨立 Database),完全隔離。
方案一:單一資料庫 (Shared Database) —— 快速卻危險的「雅房」模式
這是最常見,也是初期開發最快的方式。所有的租戶資料都存在同一個 Database 中,每一個 Table (如 orders, products) 都會有一個 tenant_id 欄位。
優點:
- 成本極低: 不用管資料庫連線切換,主機資源利用率最高。
- 維護方便: 跑
php artisan migrate一次就搞定,不用對著 100 個資料庫跑迴圈。 - 跨租戶分析容易: 老闆想看「全平台總營業額」,一行 SQL 就出來了。
缺點:
- 資料外洩風險高: 這是工程師的惡夢。只要你的
WHERE tenant_id = ?少寫一次,A 客戶就會看到 B 客戶的訂單。這在 GDPR 或醫療法規下是絕對死刑。 - 備份困難: 如果大客戶 A 說他誤刪資料要回溯,你很難只還原他的資料而不影響其他人。
Laravel 實作關鍵:Global Scopes
在 Laravel 中,為了避免工程師「忘記」寫 where('tenant_id', $id),我們通常會利用 Eloquent 的 Global Scopes 來自動注入過濾條件。
首先,建立一個 Scope:
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use App\Services\TenantManager;
class TenantScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
// 假設你有一個 Service 負責管理目前是哪個租戶
$tenantId = app(TenantManager::class)->getTenantId();
if ($tenantId) {
$builder->where('tenant_id', $tenantId);
}
}
}
接著,在你的 Model (或是建立一個 Base Model) 中引用它:
namespace App\Models;
use App\Scopes\TenantScope;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected static function booted()
{
static::addGlobalScope(new TenantScope);
// 自動在建立資料時寫入 tenant_id
static::creating(function ($model) {
$model->tenant_id = app(TenantManager::class)->getTenantId();
});
}
}
這樣一來,當你呼叫 Product::all() 時,Laravel 會自動轉成 SELECT * FROM products WHERE tenant_id = 1。這就是所謂的「防呆機制」。
方案二:獨立資料庫 (Database per Tenant) —— 尊爵不凡的「套房」模式
這是企業級 SaaS (如 Salesforce) 偏好的模式。每當有一個新客戶註冊,系統會自動建立一個全新的 Database。
優點:
- 安全性最高: 物理層級的隔離。就算程式碼有 SQL Injection 漏洞,駭客也只能撈到該租戶的資料,無法跨庫攻擊。
- 備份與還原靈活: 客戶要付費還原昨天的資料?沒問題,直接 Dump 他的 DB 就好。
- 效能擴展: 如果某個客戶流量爆衝,可以把他的 Database 搬到一台獨立的高規格 RDS 上,不影響其他小客戶。
缺點:
- Migration 地獄: 當你發布新功能要新增欄位時,你需要對 1,000 個資料庫跑 Migration。如果第 500 個失敗了,你的部署流程會變得很複雜。
- 連線數爆炸: 每個 Database 都要消耗連線資源,資料庫伺服器的配置要夠高。
Laravel 實作關鍵:動態切換 Connection
這通常需要依賴 Middleware 來識別進來的 Domain,然後即時切換 Config。
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Config;
use App\Models\Tenant;
class SwitchTenantDatabase
{
public function handle($request, Closure $next)
{
// 1. 從 Subdomain 辨識租戶
$host = $request->getHost();
$subdomain = explode('.', $host)[0];
$tenant = Tenant::where('subdomain', $subdomain)->firstOrFail();
// 2. 動態設定資料庫連線
Config::set('database.connections.tenant', [
'driver' => 'mysql',
'host' => $tenant->db_host,
'database' => $tenant->db_name,
'username' => $tenant->db_user,
'password' => $tenant->db_password,
// ... 其他設定
]);
// 3. 清除並重新連線
DB::purge('tenant');
DB::reconnect('tenant');
// 設定預設連線
DB::setDefaultConnection('tenant');
return $next($request);
}
}
除了資料庫,還有這些「坑」
Eric 必須提醒大家,多租戶系統最麻煩的往往不是資料庫,而是周邊設施:
1. Redis Cache 與 Session
如果你用 Redis 存 Session 或 Cache,記得要加上 Prefix。否則 A 客戶登入後,Session ID 剛好跟 B 客戶撞到(雖然機率低),或者 Cache key 叫做 homepage_data,結果大家看到同一份快取,那就尷尬了。
2. 檔案儲存 (Storage)
上傳圖片時,千萬不要只用 /uploads/avatar.jpg。一定要分資料夾:/tenant_1/uploads/avatar.jpg。若是用 S3,這更是基本功。
3. Queue (隊列)
這是最容易被忽略的!當你把 Job 丟到 Queue 裡時,Worker 處理那一刻,它怎麼知道要連線到哪個 Database?
如果你用 spatie/laravel-multitenancy 這種套件,它會幫你在 Job Payload 裡註記 tenant_id,Worker 啟動時會自動切換環境。如果是手刻,記得在 Job 的 handle() 方法裡重新執行一次 Tenant 的切換邏輯。
Eric 的選型建議
寫了這麼多 Code,到底該怎麼選?
- 選單一資料庫 (Shared): 如果你是 B2C 平台、預算有限、預期會有海量免費帳戶(數萬個以上),且資料結構單純。例如:個人部落格平台、簡單的記帳軟體。
- 選獨立資料庫 (Isolated): 如果你是 B2B SaaS、客戶付費意願高、極度重視資安、未來可能需要針對大客戶做客製化。例如:ERP 系統、醫療診所管理系統、企業 CRM。
總結
Laravel 在多租戶的生態系已經非常成熟。如果你不想自己造輪子,我強烈推薦研究 Stancl/Tenancy 或 Spatie/Laravel-Multitenancy 這兩個套件。它們幫你解決了 Migration、Queue、Redis Prefix 等 90% 的髒活。
但在引入套件之前,身為工程師的我們,必須理解底層的運作邏輯,才能在系統出錯時(相信我,一定會出錯),知道是哪個環節的「門」沒關好。
常見問題 (FAQ)
Q1: 已經開發好的單一租戶系統,改成多租戶會很難嗎?
非常痛苦。尤其是如果你選擇「單一資料庫」模式,你需要檢查每一行 Eloquent 查詢,確保都有加上 tenant_id 的過濾。如果選擇「獨立資料庫」模式,雖然程式碼改動較少,但需要重構 Migration 和部署流程。建議在專案啟動初期就決定好。
Q2: 獨立資料庫模式下,如何執行 Migration?
通常會撰寫一個自訂的 Artisan Command,利用迴圈遍歷所有 Tenant,並動態切換連線來執行 migrate 指令。例如:php artisan tenants:migrate。
Q3: 多租戶系統的圖片資源該如何處理?
建議在 Storage 層級(如 AWS S3 或 Local Disk)為每個租戶建立獨立的目錄(Directory),例如 /storage/{tenant_id}/...。這樣不僅管理方便,也能在需要時輕鬆計算個別租戶的硬碟使用量。
您的 SaaS 專案架構卡關了嗎?
資料庫設計一但走錯方向,未來的技術債將難以償還。浪花科技擁有豐富的 Laravel 企業級系統開發經驗,協助您做出最正確的架構決策。






