Ошибка 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/ (не рекомендуется для продакшена).