Ошибка SQLite database is locked в ASP.NET контейнере: причины и решение

    При развёртывании ASP.NET приложения в Docker контейнере с SQLite базой данных, разработчики часто сталкиваются с ошибкой SQLite Error 5: 'database is locked'. Особенно остро проблема проявляется, когда файл базы данных (.db) подключается через volume. В этой статье мы подробно разберём, почему возникает блокировка и как её устранить.

    Почему возникает ошибка database is locked?

    SQLite - это легковесная встраиваемая СУБД, которая использует файловую блокировку для обеспечения целостности данных. Когда несколько процессов или потоков одновременно пытаются записать данные в один файл, SQLite блокирует базу. В контейнерной среде с volume это случается чаще по нескольким причинам:

    • Конфликт блокировок на уровне файловой системы - volume может использовать файловую систему хоста (например, ext4 или NTFS), которая по-разному обрабатывает блокировки SQLite.
    • Одновременный запуск нескольких экземпляров - если контейнер перезапускается или масштабируется, несколько процессов могут пытаться выполнить миграцию.
    • Долгая миграция - при выполнении dbContext.Database.Migrate() SQLite создаёт временную таблицу __EFMigrationsLock для эксклюзивной блокировки, что может привести к таймауту.

    Как исправить ошибку в ASP.NET Core?

    1. Используйте синглтон для контекста базы данных

    Убедитесь, что ApplicationDbContext не создаётся повторно во время миграции. В вашем коде миграция выполняется в using (IServiceScope scope = ...), что корректно. Однако проверьте, не запускается ли миграция в фоновом сервисе или контроллере одновременно.

    2. Настройте режим блокировки SQLite

    Добавьте в строку подключения параметры, которые уменьшают вероятность блокировки:

    "DefaultConnection": "Data Source=Database/ImageCache.db;Cache=Shared;Mode=ReadWriteCreate;"

    Параметр Cache=Shared позволяет нескольким соединениям использовать общий кэш, а Mode=ReadWriteCreate гарантирует создание файла, если его нет.

    3. Добавьте повторные попытки при миграции

    Оберните вызов Migrate() в логику повторных попыток с задержкой:

    using (IServiceScope scope = app.Services.CreateScope()) {
        ApplicationDbContext dbContext = scope.ServiceProvider.GetRequiredService();
        int retries = 3;
        while (retries > 0) {
            try {
                dbContext.Database.Migrate();
                break;
            } catch (SqliteException ex) when (ex.SqliteErrorCode == 5) {
                retries--;
                Thread.Sleep(1000);
            }
        }
    }

    4. Измените конфигурацию volume

    Попробуйте использовать named volume вместо bind mount. Named volumes управляются Docker и лучше работают с блокировками SQLite:

    docker run -v myapp_data:/App/ImageCacheService/Database ...

    Альтернативные решения

    Если проблема сохраняется, рассмотрите следующие варианты:

    • Используйте PostgreSQL или SQL Server - эти СУБД не подвержены файловым блокировкам и лучше подходят для продакшена.
    • Выполняйте миграцию отдельно - запускайте миграцию как отдельный скрипт до старта приложения.
    • Отключите миграцию при запуске - если схема БД стабильна, удалите вызов Migrate() из Program.cs.

    Проверка прав доступа к файлу

    Убедитесь, что у пользователя внутри контейнера есть права на запись в папку volume. В Linux контейнерах часто используется пользователь app с UID 1000. Проверьте владельца папки на хосте:

    ls -la /путь/к/volume/

    При необходимости измените права: chmod 777 /путь/к/volume/ (не рекомендуется для продакшена).

    Часто задаваемые вопросы