Laravel多租户用数据库隔离吗

wen PHP项目 20

本文目录导读:

Laravel多租户用数据库隔离吗

  1. 三种常见多租户实现策略对比
  2. 为什么推荐“数据库隔离”?
  3. 如何在 Laravel 中实现“数据库隔离”?
  4. 一些成熟的 Laravel 多租户扩展包
  5. 什么时候用数据库隔离?

在 Laravel 中实现多租户,数据库隔离(每个租户一个独立数据库)是非常主流且推荐的做法,尤其适用于对数据安全性和隔离性要求高的场景。

但“数据库隔离”只是多租户的一种实现策略,Laravel 并没有强制规定,具体选择哪种方式,取决于你的业务需求。

下面给你详细梳理 Laravel 多租户的几种常见实现方式,以及数据库隔离的优缺点和实现思路。


三种常见多租户实现策略对比

策略 核心思想 数据隔离级别 复杂度 适用场景
数据库隔离 每个租户一个独立数据库 最高 高 (需管理多个连接) 金融、SaaS、对数据安全要求极高的场景
Schema/表空间隔离 同一个数据库,每个租户一个独立 Schema MySQL 没有严格 Schema 概念,主要用 PostgreSQL
行级隔离 同一个数据库/表,通过 tenant_id 字段区分 最低 小型SaaS、内部工具、对成本敏感的场景

为什么推荐“数据库隔离”?

虽然实现更复杂,但好处非常突出:

  • 数据安全性最高:一个租户的数据库被黑客攻击/误操作,不会影响其他租户。
  • 备份/恢复粒度细:可以为每个租户单独备份、回滚。
  • 性能隔离:某个租户的慢查询不会拖垮整个系统,可以针对大租户配置更高性能的数据库实例。
  • 维护方便:迁移、升级、数据清理都可以独立进行。

如何在 Laravel 中实现“数据库隔离”?

核心思路是:根据当前请求的租户,动态切换 Laravel 使用的数据库连接

步骤概览

  1. 数据库配置:在 config/database.php 中定义一个“动态”的连接(通常命名为 tenant),或者预定义所有租户连接。
  2. 租户识别:在中间件中解析当前请求属于哪个租户(通过域名、子域名、Header、子路径等)。
  3. 动态切换连接:将解析出的租户数据库信息(host, database, username, password)动态设置到 Laravel 的数据库管理器中。
  4. 后续查询自动使用新连接: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/TenancyHyn/Multi-tenant,避免重复造轮子。
  • 如果是小型项目、Demo、或内部工具,可以考虑更简单的“行级隔离”。

你的项目目前处于什么阶段?如果是刚开始搭建 SaaS,把多租户设计提前规划好,后期能省很多麻烦。

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