Laravel执行CREATE DATABASE IF NOT EXISTS后migrate迁移报错
问题背景
- 运行环境:Laravel 8 + Sail
- 开发目标:实现自定义命令批量创建测试数据库,适配parallel tests并行测试功能与
DatabaseTransactiontrait。由于迁移操作耗时较长,测试运行过程中动态建库执行迁移无法满足性能要求,因此需要提前完成建库+全量迁移操作。
问题中给出的初始实现代码如下:
$database='mydb_test_1'; Config::set('database.connections.mysql.database', null); $query = "CREATE DATABASE IF NOT EXISTS $database CHARACTER SET $charset COLLATE $collation;"; DB::statement($query); Config::set('database.connections.mysql.database', $database); // Necessary to update database name, as DB is a singleton DB::reconnect(); $this->call('migrate');
代码运行的异常现象:
- 目标数据库首次不存在时,代码可正常运行完成建库和迁移
- 第二次执行命令就会抛出运行错误
- 如果注释掉
DB::statement($query);这行建库代码,无论执行多少次命令,都会返回Nothing to migrate.提示,无任何报错
故障原因
核心问题出在连接状态的污染没有被完全清理:
- 执行建库语句前,代码把
mysql连接的database配置设为null,此时建立的MySQL连接没有绑定默认数据库,是直接连到MySQL服务实例层面执行建库操作。 - 第一次运行时,建库完成后调用
DB::reconnect(),新库是空的,迁移表不存在,迁移命令可以正常在新库上执行全量迁移,流程没有触发异常。 - 第二次运行时,目标库已经存在,
CREATE DATABASE IF NOT EXISTS语句本身不会报错,但此时连接仍然处于无默认库的状态。DB::reconnect()方法只会尝试重连,不会完全销毁旧的连接实例,加上Laravel迁移命令内部会缓存连接状态、部分场景下会复用旧的连接上下文,最终导致执行迁移查询时没有选中目标数据库,触发报错。 - 注释掉建库语句后,全程不会把连接切到无默认库的状态,迁移命令可以正常连接到已有目标库,检测到所有迁移已经执行完成,自然只会返回
Nothing to migrate.,不会触发错误。
修复方案
不要通过修改默认连接配置为null的方式执行建库,改用独立连接执行建库语句,同时用更彻底的连接清理方法替代reconnect(),避免连接状态污染:
$database = 'mydb_test_1'; $charset = config('database.connections.mysql.charset', 'utf8mb4'); $collation = config('database.connections.mysql.collation', 'utf8mb4_unicode_ci'); // 用独立PDO连接执行建库,不污染项目默认DB连接的状态 $pdo = new \PDO( sprintf( "mysql:host=%s;port=%s", config('database.connections.mysql.host'), config('database.connections.mysql.port') ), config('database.connections.mysql.username'), config('database.connections.mysql.password') ); $pdo->exec(sprintf( "CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET %s COLLATE %s;", $database, $charset, $collation )); // 完全销毁旧连接实例,强制下次使用全新配置初始化连接 Config::set('database.connections.mysql.database', $database); DB::purge('mysql'); // 明确指定连接执行迁移,避免走缓存的连接配置 $this->call('migrate', ['--database' => 'mysql']);
关键调整点:
- 建库操作使用独立PDO连接执行,全程不修改默认连接的配置,避免无库状态污染后续流程
- 用
DB::purge('mysql')替代DB::reconnect(),会完全删除旧的连接单例,确保后续连接完全使用最新的配置初始化 - 调用迁移命令时明确指定
--database参数,强制迁移命令使用指定的mysql连接,不会读取缓存的旧连接配置
内容的提问来源于stack exchange,提问作者Adam
相关产品推荐
相关产品推荐

