因为 laravel 原生 migrate 命令只面向单库,不区分租户,读取全局 database.php 默认连接,无法为 tenant_001、tenant_002 等各租户独立、安全、可追溯地执行迁移。

多租户迁移工具为什么不能直接用 php artisan migrate
因为 Laravel 原生 migrate 命令只面向单库,不区分租户;它读取的是全局 config/database.php 里的默认连接,所有迁移都跑在同一个数据库上。多租户场景下,你得为每个租户(比如 tenant_001、tenant_002)单独执行迁移,且不能串库、不能漏租户、不能重复执行。
常见错误现象:Base table not found 看似是表没建,其实是命令跑到了主库或空库;Class 'CreateUsersTable' not found 往往是因为租户隔离后,autoload 路径没重置,或 migration 类被缓存到错误命名空间。
- 必须动态切换 PDO 连接实例,而不是靠环境变量硬切
- 迁移文件路径需按租户隔离(如
database/migrations/tenant_001/),否则多个租户共用同一组文件会冲突 - 不能依赖
.env中的DB_DATABASE,它无法表达“当前租户用哪个库”,得从外部传入连接参数
composer require 装什么包才真正支持多租户
别装 laravel/framework 就以为能开干——它的迁移器不暴露连接切换接口;也别盲目装 doctrine/migrations,它默认只管一个 doctrine.dbal.url,不支持运行时换库。
真正可用的组合只有两种:
- 本地封装
illuminate/database:装composer require illuminate/database,然后自己 newConnectionFactory+Migrator,手动传入每个租户的 PDO 实例 - 用
robmorgan/phinx+ 自定义 adapter:装composer require --dev robmorgan/phinx,再写一个支持动态 host/dbname 的MultiTenantAdapter类,继承Phinx\Db\Adapter\MysqlAdapter并重写connect()
注意:topthink/think-migration 不支持运行时切换数据库,TP6 的 migrate:run 命令只认 config/database.php 里固定配置,不适合此场景。
如何让 Composer scripts 安全触发多租户迁移
把 post-update-cmd 直接写成 @php artisan migrate --force 是危险的——它只会跑一次,且永远跑在默认库上。要支持多租户,必须把租户列表作为参数传进去,且确保 autoload 已就绪。
推荐做法是写一个独立 PHP 脚本,例如 scripts/run-tenant-migrations.php:
#!/usr/bin/env php
<?php require __DIR__.'/../vendor/autoload.php';
$tenants = ['tenant_001', 'tenant_002', 'tenant_003'];
foreach ($tenants as $tenant) {
$connection = new PDO("mysql:host=127.0.0.1;dbname={$tenant}", 'root', '');
// ... 初始化 Migrator 或 Phinx 对象,指定 migrations 路径为 "database/migrations/{$tenant}/"
$migrator->run();
}
然后在 composer.json 中这样配:
- 确保脚本在
post-autoload-dump钩子执行,而非post-update-cmd——避免类找不到 - 命令写成
"php scripts/run-tenant-migrations.php",不要用@php,因为脚本里已 require autoload - 生产环境加判断:
if (getenv('APP_ENV') === 'production') { ... },防止开发机误跑
租户迁移状态怎么不互相污染
Doctrine Migrations 默认只建一张 doctrine_migration_versions 表,所有租户共享这个元数据表,结果就是 A 租户迁了,B 租户查状态时显示“已执行”,但实际 B 库里根本没建表——这是最隐蔽的数据不一致来源。
Laravel 的 migrations 表同理,默认叫 migrations,如果多个租户共用一个表名,状态就全乱了。
- 每个租户必须有独立的 migrations 表,命名规则如
migrations_tenant_001,并在初始化 Migrator 时通过setRepository(new DatabaseRepository(...))指定 - Phinx 支持
--table参数:vendor/bin/phinx migrate -e tenant_001 --table migrations_tenant_001 - 千万别用
migrate:fresh或migrate:reset批量操作——它们不会按租户边界执行,极易清掉其他租户的表
真正的难点不在“怎么跑”,而在“怎么证明每个租户都跑了、且只跑了一次”。状态表隔离只是起点,你还得记录租户 ID、执行时间、migration 文件哈希,否则上线回滚时根本没法定位哪几个租户漏了步骤。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











