本文目录导读:

在 Laravel 中实现多租户,数据库隔离(每个租户一个独立数据库)是非常主流且推荐的做法,尤其适用于对数据安全性和隔离性要求高的场景。
但“数据库隔离”只是多租户的一种实现策略,Laravel 并没有强制规定,具体选择哪种方式,取决于你的业务需求。
下面给你详细梳理 Laravel 多租户的几种常见实现方式,以及数据库隔离的优缺点和实现思路。
三种常见多租户实现策略对比
| 策略 | 核心思想 | 数据隔离级别 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 数据库隔离 | 每个租户一个独立数据库 | 最高 | 高 (需管理多个连接) | 金融、SaaS、对数据安全要求极高的场景 |
| Schema/表空间隔离 | 同一个数据库,每个租户一个独立 Schema | 高 | 中 | MySQL 没有严格 Schema 概念,主要用 PostgreSQL |
| 行级隔离 | 同一个数据库/表,通过 tenant_id 字段区分 |
最低 | 低 | 小型SaaS、内部工具、对成本敏感的场景 |
为什么推荐“数据库隔离”?
虽然实现更复杂,但好处非常突出:
- 数据安全性最高:一个租户的数据库被黑客攻击/误操作,不会影响其他租户。
- 备份/恢复粒度细:可以为每个租户单独备份、回滚。
- 性能隔离:某个租户的慢查询不会拖垮整个系统,可以针对大租户配置更高性能的数据库实例。
- 维护方便:迁移、升级、数据清理都可以独立进行。
如何在 Laravel 中实现“数据库隔离”?
核心思路是:根据当前请求的租户,动态切换 Laravel 使用的数据库连接。
步骤概览
- 数据库配置:在
config/database.php中定义一个“动态”的连接(通常命名为tenant),或者预定义所有租户连接。 - 租户识别:在中间件中解析当前请求属于哪个租户(通过域名、子域名、Header、子路径等)。
- 动态切换连接:将解析出的租户数据库信息(host, database, username, password)动态设置到 Laravel 的数据库管理器中。
- 后续查询自动使用新连接:Eloquent 模型和 DB Facade 都会自动使用切换后的连接。
代码示例(简化版)
假设我们使用 子域名 识别租户(tenant1.yourapp.com)。
Step 1: 创建租户配置表(中心数据库)
你需要一个主数据库来存储所有租户的元数据(如域名、数据库连接信息、名称等)。
// 主库表结构 (central database)
Schema::create('tenants', function (Blueprint $table) {
$table->id();
$table->string('domain')->unique(); // 'tenant1'
$table->string('database_name'); // 'tenant1_db'
$table->string('database_host'); // '127.0.0.1'
$table->string('database_username');
$table->string('database_password');
$table->timestamps();
});
Step 2: 创建中间件
// app/Http/Middleware/TenantDatabaseMiddleware.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use App\Models\Tenant;
class TenantDatabaseMiddleware
{
public function handle($request, Closure $next)
{
// 1. 从子域名获取租户标识
$domain = explode('.', $request->getHost())[0];
// 2. 从主库查找租户信息(缓存起来避免每次查)
$tenant = Tenant::where('domain', $domain)->firstOrFail();
// 3. 动态配置一个新的数据库连接(名为 'tenant')
Config::set('database.connections.tenant', [
'driver' => 'mysql',
'host' => $tenant->database_host,
'database' => $tenant->database_name,
'username' => $tenant->database_username,
'password' => $tenant->database_password,
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
]);
// 4. 将默认连接切换到 'tenant'
Config::set('database.default', 'tenant');
// 5. (可选)清除之前可能已建立的连接缓存
DB::purge('tenant');
return $next($request);
}
}
Step 3: 注册中间件
在 app/Http/Kernel.php 中注册中间件(注意要放在全局或路由组中)。
Step 4: 后续操作
所有模型和查询都会自动使用这个动态配置的 tenant 连接。
// 在 TenantA 的控制器中 $users = User::all(); // 自动查询 tenant1_db.users
一些成熟的 Laravel 多租户扩展包
手动实现如上代码非常繁琐且容易出错,建议直接使用社区成熟的包,它们已经帮你处理了大部分边缘情况。
- Stancl/Tenancy (推荐,Star 最高,功能最全)
- 支持数据库隔离(每个租户一个数据库)和行级隔离。
- 自动处理队列、事件、缓存、文件系统隔离。
- GitHub:
https://github.com/stancl/tenancy
- Hyn/Multi-tenant
- 老牌包,功能稳定。
- 主要基于数据库隔离。
- GitHub:
https://github.com/hyn/multi-tenant
通常推荐使用 Stancl/Tenancy,它文档完善,社区活跃,且更新及时。
什么时候不用数据库隔离?
虽然数据库隔离很好,但如果你遇到以下情况,可以考虑行级隔离(tenant_id):
- 项目规模极小:只有几十个用户,数据量不大。
- 预算有限:不想为每个租户单独购买/维护数据库实例。
- 开发速度优先:行级隔离实现最简单,开发最快。
- 数据隐私要求不高:内部工具或B2B但客户不介意数据混存。
行级隔离的典型做法:所有表增加 tenant_id 列,每个查询都自动添加 WHERE tenant_id = ? 条件,可以用 Laravel 的 Global Scope 实现。
- 对于大多数正式的 SaaS 应用,强烈建议使用“数据库隔离”(每个租户一个独立数据库)。
- 最好使用成熟的扩展包如 Stancl/Tenancy 或 Hyn/Multi-tenant,避免重复造轮子。
- 如果是小型项目、Demo、或内部工具,可以考虑更简单的“行级隔离”。
你的项目目前处于什么阶段?如果是刚开始搭建 SaaS,把多租户设计提前规划好,后期能省很多麻烦。