SQLite LinuxWindowsmacOS

SQLite: database is locked при одновременной записи

  • Обновлено
  • Чтение ≈ 5 минут

Краткий ответ

Ошибка SQLite «database is locked» (SQLITE_BUSY) возникает при параллельной записи. Включите режим WAL, задайте busy_timeout, сократите длительность транзакций, избегайте записи во время чтения и проверьте права на файл и каталог базы.

В этой статье
  1. Что происходит
  2. Причины
  3. Порядок
  4. Типичный сценарий на сайте
SQLITE_BUSY: database is locked
SQLSTATE[HY000]: General error: 5 database is locked
По-русскиSQLite не может получить блокировку на запись.

#Что происходит

SQLite допускает одну запись одновременно. Если один процесс пишет, а другой пытается записать или читает в режиме журнала rollback, второй получает ошибку database is locked (код 5, SQLITE_BUSY). В PHP это исключение PDO. Частые причины: длительные транзакции, незакрытые запросы, отсутствие тайм-аута ожидания, режим журнала DELETE при высокой конкурентности, база на сетевом диске (NFS, SMB), у веб-сервера нет прав записи в каталог (SQLite создаёт временные файлы журнала рядом с базой).

#Причины

Возможная причинаКак узнатьЧто делать
Нет busy_timeoutПо умолчанию 0.Ошибка сразу.PRAGMA busy_timeout.
Режим DELETEЧитатели блокируют писателя.Ошибки при нагрузке.Режим WAL.
Долгие транзакцииОперации внутри.Блокировки на секунды.Укоротите транзакции.
Нет прав на каталогЖурнал.Ошибка запись/readonly.Права www-data.
База на сетевом дискеNFS.Случайные блокировки.Локальный диск.
Незакрытый запросКурсор.Блокировка остаётся.Закройте.

#Порядок

  1. Включите режим WAL: читатели не блокируют писателяSQLite допускает множество читателей, но одновременно только одного писателя, и WAL не отменяет это ограничение. Администратору приложения сначала нужно сделать согласованную резервную копию базы через SQLite Backup API или команду .backup при доступном подключении. После этого в обслуживаемом локальном приложении проверьте PRAGMA journal_mode;, затем при подходящих условиях включите PRAGMA journal_mode=WAL;. WAL требует возможность создавать рядом с БД файлы -wal и -shm; не применяйте его на неподходящей сетевой файловой системе.
  2. Задайте busy_timeoutВ коде при открытии КАЖДОГО соединения установите ожидание блокировки: для PDO SQLite выполните $pdo->exec('PRAGMA busy_timeout = 5000'); (в миллисекундах), либо используйте соответствующую настройку драйвера. Если другой процесс держит запись слишком долго, это не устранит корень проблемы, но позволит дождаться короткой транзакции. Проверьте в журнале, исчезли ли ошибки database is locked, и измерьте реальные задержки.
  3. Выполняйте запись короткими транзакциямиОткройте код участка, который начинает транзакцию, и проверьте, что между BEGIN и COMMIT не выполняются HTTP-запросы, ожидание пользователя или длительные вычисления. Перенесите подготовку данных до транзакции, затем быстро запишите изменения и завершите её; в блоке обработки исключения выполняйте ROLLBACK, если она активна. Это обычно полезнее, чем бесконечно увеличивать таймаут ожидания.
  4. Используйте BEGIN IMMEDIATE для транзакций, которые будут писать, чтобыЕсли транзакция заранее знает, что будет писать, в одном месте кода используйте BEGIN IMMEDIATE, чтобы попытаться получить право записи в начале. При занятой базе команда ожидает в пределах установленного busy_timeout; это не «устраняет взаимоблокировки» автоматически. Не смешивайте вручную BEGIN IMMEDIATE с управлением транзакциями PDO::beginTransaction() на том же соединении: выбирайте один способ управления и аккуратно обрабатывайте ошибки.
  5. Закрывайте курсорыПосле чтения большого набора данных убедитесь, что приложение не держит активные курсоры и подготовленные запросы дольше, чем необходимо. В PHP с PDO можно завершить чтение через $stmt->closeCursor() и убрать ссылку на объект; для языков с явным close() используйте штатный способ закрытия. После изменения повторите параллельную запись в тестовой среде и убедитесь, что транзакции короткие.
  6. Проверьте права на файл базы и каталог: веб-сервер должен писать в оба,На Linux администратор сначала выполнит ls -ld /путь/к/каталогу и ls -l /путь/к/базе.sqlite, подставив реальные пути; дополнительно проверит, под каким пользователем работает PHP-FPM/служба. В режиме WAL этот пользователь должен иметь доступ на запись к базе и каталогу для файлов -wal, -shm. Не выполняйте рекурсивный chown -R www-data и не устанавливайте chmod 777: реальные пользователи и модель доступа отличаются.
  7. Не размещайте базу на сетевом дискеЕсли файл SQLite находится на SMB/NFS или другом сетевом ресурсе, посмотрите фактический путь базы и тип файловой системы (df -T /путь/к/базе.sqlite в Linux). Блокировки и режим WAL могут работать ненадёжно между узлами; обсудите перенос базы на локальный диск сервера или переход на PostgreSQL. Перед переносом сделайте согласованную копию и переключайте приложения с остановкой записей, чтобы не потерять последние транзакции.
  8. Найдите процессы, которые держат базу открытойНа Linux в терминале выполните lsof /путь/к/базе.sqlite (или fuser), подставив реальный путь, и сопоставьте PID с известными сервисами. Команда показывает открывшие файл процессы, но не гарантирует, что именно они удерживают блокировку. Не завершайте PID командой kill без выяснения роли и текущей транзакции; сначала проверьте логи и попробуйте штатно завершить работу приложения.
  9. Если запись очень частая, ставьте её в очередь или рассмотрите PostgreSQLЕсли несколько процессов часто записывают в одну базу и при включённом WAL/коротких транзакциях очередь ожидания остаётся, организуйте одного обработчика записи (очередь сообщений или задания в базе). Для высокой конкурентности рассмотрите серверную СУБД PostgreSQL; миграция потребует проверки схемы, транзакций и резервных копий. Не пытайтесь лечить постоянную перегрузку частыми wal_checkpoint(TRUNCATE), которые могут сами конфликтовать с работающими соединениями.
  10. Команды для отдельных ситуацийВыберите только подходящий вариант. Это не одна последовательность: часть команд диагностирует проблему, а часть меняет настройки или данные.
    Выберите подходящий вариант — это не команды для последовательного выполнения
    Вариант 1Не переключайте WAL на каждом запросе веб-приложения; делайте это отдельной управляемой операцией
    PHPКоманда
    Не переключайте WAL на каждом запросе веб-приложения; делайте это отдельной управляемой операцией
    <?php
    // Обычное соединение: настройка ожидания на каждом подключении.
    $pdo = new PDO('sqlite:' . __DIR__ . '/data/app.sqlite');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->exec('PRAGMA busy_timeout = 5000');
    // PRAGMA journal_mode=WAL задаётся отдельно при обслуживании,
    // после согласованной резервной копии и проверки файловой системы.
    Вариант 2Короткая транзакция записи с немедленной блокировкой
    PHPКоманда
    Короткая транзакция записи с немедленной блокировкой
    <?php
    $pdo->exec('BEGIN IMMEDIATE');
    try {
    $st = $pdo->prepare('INSERT INTO log(message, created_at) VALUES (?, ?)');
    $st->execute(['событие', date('c')]);
    $pdo->exec('COMMIT');
    } catch (Throwable $e) {
    $pdo->exec('ROLLBACK');
    throw $e;
    }
    Вариант 3Удалена рекурсивная смена владельца и chmod для всей директории
    Linux · Терминал (Bash)Команда
    Удалена рекурсивная смена владельца и chmod для всей директории
    # Диагностика без смены владельца и прав
    ls -ld data/
    ls -l data/app.sqlite*
    lsof data/app.sqlite* 2>/dev/null
    Вариант 4Проверка режима журнала и целостности из командной строки
    Linux · Терминал (Bash)Команда
    Проверка режима журнала и целостности из командной строки
    sqlite3 data/app.sqlite "PRAGMA journal_mode; PRAGMA busy_timeout; PRAGMA integrity_check;"
    Вариант 5TRUNCATE checkpoint не применять как универсальную меру против блокировки
    Linux · Терминал (Bash)Команда
    TRUNCATE checkpoint не применять как универсальную меру против блокировки
    # Только диагностика текущего режима и целостности; изменяющий checkpoint убран
    sqlite3 data/app.sqlite "PRAGMA journal_mode; PRAGMA integrity_check;"

#Типичный сценарий на сайте

Если два PHP-запроса одновременно записывают счётчики или логи, один из них упадёт с блокировкой. Решение: WAL, busy_timeout 5000 мс и короткие транзакции. Если запись идёт непрерывно (сотни операций в секунду), сгруппируйте записи пакетами или перейдите на PostgreSQL или MySQL.

Так эту ошибку тоже называют

Источники и обновления

Последнее обновление статьи: 2 ноября 2026. Версии программ и интерфейсы меняются — если шаги не совпадают с вашей версией, сверяйтесь с документацией производителя.