You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel 8+Pest测试中RefreshDatabase引发SQLite数据库锁定问题

解决Laravel 8双连接测试时SQLite锁定及表不存在问题

问题梳理

  • 生产环境:同一MariaDB实例下配置两个数据库连接(mysql和another),通过config/database.php定义
  • 测试环境:用单SQLite文件模拟双连接,phpunit.xml中配置两个连接指向同一SQLite文件
  • 关联逻辑:Project模型(绑定mysql连接)与PollProject模型(绑定another连接),通过mysql库的project_poll_project中间表建立关联
  • 测试异常:
    1. 引入RefreshDatabase trait后,beforeEach里执行attach关联操作触发SQLite锁定错误:SQLSTATE[HY000]: General error: 5 database is locked
    2. 切换为独立SQLite文件或:memory:内存库时,出现表不存在错误:SQLSTATE[HY000]: General error: 1 no such table: project_poll_project
    3. 移除RefreshDatabase并手动执行迁移后,测试初始化正常

可行解决方案

1. 让RefreshDatabase同时处理双连接迁移

RefreshDatabase默认只处理默认数据库连接的迁移,需要重写迁移逻辑,让它同时执行两个连接的迁移:

<?php

namespace Tests;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Artisan;

abstract class TestCase extends \Illuminate\Foundation\Testing\TestCase
{
    use CreatesApplication, RefreshDatabase {
        RefreshDatabase::migrate as baseMigrate;
    }

    protected function migrate()
    {
        // 执行默认mysql连接的迁移
        $this->baseMigrate();
        
        // 执行another连接的迁移(如果迁移文件单独放在migrations/another目录,就加--path参数,否则去掉)
        Artisan::call('migrate', [
            '--database' => 'another',
            '--path' => database_path('migrations/another'),
            '--force' => true,
        ]);
    }
}

这样就能保证两个连接对应的表都被正确创建,解决独立文件/内存库下表不存在的问题。

2. 解决SQLite锁定问题

SQLite是单文件数据库,跨连接的并发写入容易触发锁竞争,试试这两个方案:

方案A:测试环境禁用外键约束

在测试基类的setUp方法里添加代码,禁用两个连接的外键约束,减少锁竞争:

protected function setUp(): void
{
    parent::setUp();
    
    foreach (['mysql', 'another'] as $conn) {
        \DB::connection($conn)->statement('PRAGMA foreign_keys = OFF;');
    }
}

方案B:用内存库并共享连接实例

如果用:memory:内存库,必须让两个连接共享同一个内存实例,否则会出现两个独立的内存库,导致表找不到。

  • 先在phpunit.xml里配置:
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
<server name="DB_CONNECTION_ANOTHER" value="sqlite"/>
<server name="DB_DATABASE_ANOTHER" value=":memory:"/>
  • 然后在config/database.php的another连接配置里添加instance参数,复用默认SQLite实例:
'another' => [
    'driver' => 'sqlite',
    'database' => env('DB_DATABASE_ANOTHER', database_path('another.sqlite')),
    'prefix' => '',
    'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
    'instance' => 'default', // 共享默认连接的内存实例
],

这样两个连接共用同一个内存数据库,既避免文件锁定,又能保证所有表都存在。

3. 手动控制迁移与数据重置

如果上述方案还是有问题,直接放弃RefreshDatabase,手动在测试里控制迁移流程:

<?php

use Illuminate\Support\Facades\Artisan;

beforeEach(function () {
    // 每次测试前重置两个数据库
    Artisan::call('migrate:fresh', [
        '--database' => 'mysql',
        '--force' => true,
    ]);
    Artisan::call('migrate:fresh', [
        '--database' => 'another',
        '--force' => true,
    ]);
    
    // 执行模型创建和关联操作
    $project = \App\Models\Project::factory()->create();
    $pollProject = \App\Models\PollProject::factory()->create();
    $project->pollProjects()->attach($pollProject);
});

完全手动控制迁移,避免trait默认逻辑带来的冲突。

内容的提问来源于stack exchange,提问作者Kangel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 20:45:35