SQLite SQLitePHPPythonNode.js SQLITE_BUSYSQLITE_LOCKED

SQLite: ошибка «database is locked» — как исправить

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

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

SQLITE_BUSY означает конфликт доступа к файлу базы. Сначала завершите зависшие транзакции и задайте busy_timeout на каждом соединении; WAL включайте отдельно при резервной копии, поддерживаемой локальной файловой системе и проверке приложений.

В этой статье
  1. Что означает ошибка
  2. Частые причины
  3. Пошаговое устранение SQLITE_BUSY
  4. Способ 1. Настройте ожидание и рассмотрите WAL при необходимости
  5. PHP (PDO)
  6. Python
  7. Способ 2. Делайте транзакции короткими и начинайте запись сразу
  8. Способ 3. Найдите того, кто держит блокировку
  9. Способ 4. Организуйте одного писателя
  10. Как проверить результат
  11. Если не помогло

#Что означает ошибка

SQLITE_BUSY (5)
SQLSTATE[HY000]: General error: 5 database is locked
По-русскиФайл базы данных заблокирован другим соединением.

SQLite разрешает много одновременных читателей, но писать может только одно соединение за раз. Если вторая запись начинается, пока первая не завершена, SQLite ждёт столько, сколько вы разрешили (busy_timeout), а затем возвращает SQLITE_BUSY. Родственный код SQLITE_LOCKED («database table is locked») означает конфликт внутри одного соединения, например незавершённый запрос на чтение той же таблицы.

#Частые причины

  • Длинная транзакция записи или забытая незавершённая транзакция.
  • Одновременный импорт и обработка запросов сайта без ожидания (busy_timeout = 0).
  • Режим журнала DELETE по умолчанию: запись блокирует читателей.
  • Чтение внутри транзакции, которая затем пытается стать записью (повышение блокировки).
  • База лежит на сетевой файловой системе без корректных блокировок.

#Пошаговое устранение SQLITE_BUSY

  1. Уточните файл базы и сохраните данныеВ настройках приложения найдите путь к файлу SQLite и убедитесь, что это нужная база, а не копия или тестовый файл. Перед изменением режима журнала согласуйте обслуживание и сделайте резервную копию средствами самого приложения или штатным механизмом резервирования SQLite. Не копируйте произвольно только основной .sqlite из работающей WAL-базы и не удаляйте файлы -wal и -shm: там могут находиться неподтверждённые в основном файле изменения.
  2. Найдите источник параллельной записиПроверьте, не запущены ли одновременно импорт, планировщик задач, два экземпляра сайта или несколько фоновых работников. На Linux по согласованию с администратором выполните fuser -v /путь/к/app.sqlite, подставив реальный путь и учитывая, что права доступа могут ограничивать вывод. Наличие открытого файла само по себе не доказывает блокировку: сопоставьте время ошибки с журналами транзакций приложения. Не завершайте чужие процессы наугад.
  3. Настройте ожидание блокировки на каждом подключенииВ коде открытия соединения (PHP PDO или Python sqlite3) установите время ожидания 5 секунд, например PRAGMA busy_timeout = 5000; после открытия SQLite. Для PDO можно указать PDO::ATTR_TIMEOUT => 5, для Python — sqlite3.connect(..., timeout=5). Важно: время ожидания задаётся отдельно для каждого соединения. Проверьте текущее значение командой PRAGMA busy_timeout;: в этом же соединении ожидается 5000 миллисекунд. Если параллельная транзакция работает дольше, одно ожидание не устранит причину.
  4. Сократите транзакции и правильно обрабатывайте ошибкиВ коде приложения перенесите долгие вычисления и сетевые запросы за пределы транзакции. После записи сразу выполняйте COMMIT, а при исключении — ROLLBACK и закрывайте курсоры. При сценарии, когда транзакция сразу должна писать, можно использовать BEGIN IMMEDIATE, чтобы попытаться получить блокировку записи в начале; если она занята, операция всё равно может вернуть SQLITE_BUSY. Не запускайте демонстрационный SQL над рабочей базой без понимания его последствий.
  5. Рассмотрите режим WAL только для подходящего окруженияЕсли база находится на локальном диске, приложение совместимо с WAL и сделана резервная копия, администратор в окно обслуживания может проверить PRAGMA journal_mode; и при необходимости выполнить PRAGMA journal_mode=WAL;. Вывод wal означает, что режим включён. Для сетевой файловой системы с доступом из разных машин этот режим не подходит. Режим уменьшает конкуренцию читателей и писателя, но не позволяет нескольким писателям одновременно изменять SQLite.
  6. Повторите исходную операцию и проверьте журналыПосле изменения кода и настройки соединений повторите именно ту загрузку или запись, которая раньше вызывала database is locked. Проверьте отсутствие SQLITE_BUSY в журнале на обычной и умеренно параллельной нагрузке, а также корректность сохранённых данных. Если ошибка осталась, соберите время, путь базы, сведения об активных транзакциях и количество пишущих процессов; при постоянной конкуренции оцените очередь записи или переход на серверную СУБД.

#Способ 1. Настройте ожидание и рассмотрите WAL при необходимости

Сначала исправьте длительные транзакции и настройте ожидание блокировки на каждом подключении. WAL может уменьшить конфликты между читателями и писателем, но одновременно пишущим соединениям всё равно нужна координация. Переключение journal_mode меняет режим для всего файла базы: выполняйте его один раз, после резервного копирования и проверки совместимости приложения, в согласованное окно без активных пишущих операций. На сетевых ФС WAL не подходит для клиентов с разных машин.

SQLКоманда
Это альтернативные действия: busy_timeout задавайте на каждом соединении, journal_mode переводите в WAL только один раз после проверки условий.
PRAGMA busy_timeout = 5000;
-- Следующая команда только для планового перехода на WAL после резервной копии:
PRAGMA journal_mode = WAL;

#PHP (PDO)

PHP · подключение
Путь замените на действительный. Настройки ожидания на каждом соединении; переход в WAL выполняется отдельно, а не при каждом HTTP-запросе.
$pdo = new PDO('sqlite:/path/to/app.sqlite', null, null, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_TIMEOUT => 5,
]);
$pdo->exec('PRAGMA busy_timeout = 5000');

#Python

Python · подключение
Имя базы замените на реальное. Не повторяйте переключение journal_mode при каждом открытии соединения.
import sqlite3
conn = sqlite3.connect('app.sqlite', timeout=5)
conn.execute('PRAGMA busy_timeout = 5000')

#Способ 2. Делайте транзакции короткими и начинайте запись сразу

Выполняйте подготовку данных до начала транзакции. Если транзакция точно будет изменять данные, начинайте её с BEGIN IMMEDIATE: блокировка записи берётся сразу, поэтому не возникает именно конфликт повышения блокировки из чтения, но SQLITE_BUSY всё ещё возможен, если другой процесс удерживает блокировку записи.

SQLКоманда
Схематичный пример; если возникла ошибка между BEGIN и COMMIT, выполните ROLLBACK и закройте соединение по логике приложения. Не копируйте пример в рабочую базу без проверки.
BEGIN IMMEDIATE;
-- Здесь выполняйте только ваши реальные SQL-запросы изменения данных
COMMIT;

#Способ 3. Найдите того, кто держит блокировку

  • Проверьте, не запущен ли второй экземпляр приложения, фоновая задача или импорт, обращающиеся к тому же файлу.
  • Убедитесь, что все курсоры закрыты, а транзакции завершены — в том числе при исключениях (используйте try/finally).
  • На Linux посмотрите, какие процессы открыли файл базы.
Linux · Терминал (Bash)Команда
Только диагностика на Linux, замените путь на настоящий; при WAL проверьте также открытые -wal/-shm. Не завершайте найденные процессы, не согласовав остановку приложения.
fuser -v /path/to/app.sqlite

#Способ 4. Организуйте одного писателя

Для импорта больших пакетов используйте очередь и один процесс записи, а публичные запросы оставьте только на чтение. Так сайт на SQLite стабильно работает под нагрузкой.

#Как проверить результат

  • Нагрузочный тест (одновременные записи и чтения) проходит без SQLITE_BUSY.
  • PRAGMA journal_mode; возвращает wal, PRAGMA busy_timeout; возвращает заданное значение.

#Если не помогло

Если база хранится на сетевом диске (NFS, SMB), перенесите её на локальный диск: блокировки SQLite на сетевых ФС ненадёжны. Если одновременных писателей действительно много, оцените переход на клиент-серверную СУБД (PostgreSQL, MySQL).

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

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

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