ThinkPHP6多租户SaaS系统架构实战:动态数据库切换方案
一、多租户架构设计模式
三种主流多租户实现方案对比:
| 方案类型 | 优点 | 缺点 |
|---|---|---|
| 独立数据库 | 数据完全隔离,安全性高 | 运维成本高 |
| 共享数据库独立Schema | 平衡隔离与成本 | 需要数据库权限 |
| 共享数据表 | 成本最低 | 隔离性差 |
本文采用独立数据库+动态切换方案
二、核心实现方案
1. 数据库配置动态化
// config/database.php
return [
'default' => env('database.default', 'mysql'),
'connections' => [
'mysql' => [
'type' => 'mysql',
'hostname' => env('database.hostname', '127.0.0.1'),
'database' => env('database.database', ''),
'username' => env('database.username', 'root'),
'password' => env('database.password', ''),
// 其他公共配置...
],
// 租户数据库模板
'tenant_template' => [
'type' => 'mysql',
'hostname' => env('tenant.db_host', '127.0.0.1'),
'username' => env('tenant.db_user', 'tenant_user'),
'password' => env('tenant.db_pass', ''),
'charset' => 'utf8mb4',
'prefix' => '',
]
]
];
2. 租户识别中间件
// app/middleware/Tenancy.php
class Tenancy
{
public function handle($request, Closure $next)
{
// 从域名或请求头获取租户标识
$tenantId = $this->getTenantId($request);
// 初始化租户上下文
TenantContext::init($tenantId);
// 动态切换数据库
$this->switchDatabase($tenantId);
return $next($request);
}
protected function switchDatabase($tenantId)
{
// 获取租户专属数据库名
$dbName = 'tenant_' . $tenantId;
// 动态修改配置
config([
'database.connections.tenant' => array_merge(
config('database.connections.tenant_template'),
['database' => $dbName]
),
'database.default' => 'tenant'
]);
// 重新连接数据库
Db::connect('tenant')->reconnect();
}
}
3. 租户上下文管理
// app/common/TenantContext.php
class TenantContext
{
protected static $tenantId;
public static function init($tenantId)
{
self::$tenantId = $tenantId;
app()->bind('tenant', function() use ($tenantId) {
return TenantModel::find($tenantId);
});
}
public static function getTenantId()
{
return self::$tenantId;
}
public static function getCurrent()
{
return app('tenant');
}
}
4. 租户数据范围控制
// app/model/BaseModel.php
class BaseModel extends Model
{
protected static function boot()
{
parent::boot();
// 自动限定租户数据范围
static::addGlobalScope('tenant', function($builder) {
if ($tenantId = TenantContext::getTenantId()) {
$builder->where('tenant_id', $tenantId);
}
});
}
}
三、关键问题解决方案
1. 租户数据库自动化创建
// 使用事件监听器自动建库
Event::listen('TenantCreated', function($tenant) {
$dbName = 'tenant_' . $tenant->id;
// 使用主连接创建数据库
Db::connect('mysql')->execute(
"CREATE DATABASE IF NOT EXISTS `{$dbName}` CHARACTER SET utf8mb4"
);
// 执行基础SQL脚本
$sql = file_get_contents(app_path('database/tenant_template.sql'));
Db::connect('tenant')->execute($sql);
});
2. 跨租户数据迁移工具
class TenantDataMigrator
{
public function migrate($fromTenant, $toTenant)
{
$tables = ['users', 'products', 'orders'];
foreach ($tables as $table) {
$this->migrateTable($fromTenant, $toTenant, $table);
}
}
protected function migrateTable($from, $to, $table)
{
$data = Db::connect("tenant_$from")
->table($table)
->get()
->toArray();
Db::connect("tenant_$to")
->table($table)
->insert($data);
}
}
四、性能优化建议
- 连接池管理:使用Hyperf数据库连接池
- 配置缓存:缓存租户数据库配置
- 查询优化:避免跨库JOIN操作
- 分库策略:按租户活跃度分组部署

