#Что означает ошибка
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
- Уточните файл базы и сохраните данныеВ настройках приложения найдите путь к файлу SQLite и убедитесь, что это нужная база, а не копия или тестовый файл. Перед изменением режима журнала согласуйте обслуживание и сделайте резервную копию средствами самого приложения или штатным механизмом резервирования SQLite. Не копируйте произвольно только основной
.sqliteиз работающей WAL-базы и не удаляйте файлы-walи-shm: там могут находиться неподтверждённые в основном файле изменения. - Найдите источник параллельной записиПроверьте, не запущены ли одновременно импорт, планировщик задач, два экземпляра сайта или несколько фоновых работников. На Linux по согласованию с администратором выполните
fuser -v /путь/к/app.sqlite, подставив реальный путь и учитывая, что права доступа могут ограничивать вывод. Наличие открытого файла само по себе не доказывает блокировку: сопоставьте время ошибки с журналами транзакций приложения. Не завершайте чужие процессы наугад. - Настройте ожидание блокировки на каждом подключенииВ коде открытия соединения (PHP PDO или Python sqlite3) установите время ожидания 5 секунд, например
PRAGMA busy_timeout = 5000;после открытия SQLite. Для PDO можно указатьPDO::ATTR_TIMEOUT => 5, для Python —sqlite3.connect(..., timeout=5). Важно: время ожидания задаётся отдельно для каждого соединения. Проверьте текущее значение командойPRAGMA busy_timeout;: в этом же соединении ожидается 5000 миллисекунд. Если параллельная транзакция работает дольше, одно ожидание не устранит причину. - Сократите транзакции и правильно обрабатывайте ошибкиВ коде приложения перенесите долгие вычисления и сетевые запросы за пределы транзакции. После записи сразу выполняйте
COMMIT, а при исключении —ROLLBACKи закрывайте курсоры. При сценарии, когда транзакция сразу должна писать, можно использоватьBEGIN IMMEDIATE, чтобы попытаться получить блокировку записи в начале; если она занята, операция всё равно может вернуть SQLITE_BUSY. Не запускайте демонстрационный SQL над рабочей базой без понимания его последствий. - Рассмотрите режим WAL только для подходящего окруженияЕсли база находится на локальном диске, приложение совместимо с WAL и сделана резервная копия, администратор в окно обслуживания может проверить
PRAGMA journal_mode;и при необходимости выполнитьPRAGMA journal_mode=WAL;. Выводwalозначает, что режим включён. Для сетевой файловой системы с доступом из разных машин этот режим не подходит. Режим уменьшает конкуренцию читателей и писателя, но не позволяет нескольким писателям одновременно изменять SQLite. - Повторите исходную операцию и проверьте журналыПосле изменения кода и настройки соединений повторите именно ту загрузку или запись, которая раньше вызывала
database is locked. Проверьте отсутствиеSQLITE_BUSYв журнале на обычной и умеренно параллельной нагрузке, а также корректность сохранённых данных. Если ошибка осталась, соберите время, путь базы, сведения об активных транзакциях и количество пишущих процессов; при постоянной конкуренции оцените очередь записи или переход на серверную СУБД.
#Способ 1. Настройте ожидание и рассмотрите WAL при необходимости
Сначала исправьте длительные транзакции и настройте ожидание блокировки на каждом подключении. WAL может уменьшить конфликты между читателями и писателем, но одновременно пишущим соединениям всё равно нужна координация. Переключение journal_mode меняет режим для всего файла базы: выполняйте его один раз, после резервного копирования и проверки совместимости приложения, в согласованное окно без активных пишущих операций. На сетевых ФС WAL не подходит для клиентов с разных машин.
PRAGMA busy_timeout = 5000;-- Следующая команда только для планового перехода на WAL после резервной копии:PRAGMA journal_mode = WAL;#PHP (PDO)
$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
import sqlite3
conn = sqlite3.connect('app.sqlite', timeout=5)
conn.execute('PRAGMA busy_timeout = 5000')#Способ 2. Делайте транзакции короткими и начинайте запись сразу
Выполняйте подготовку данных до начала транзакции. Если транзакция точно будет изменять данные, начинайте её с BEGIN IMMEDIATE: блокировка записи берётся сразу, поэтому не возникает именно конфликт повышения блокировки из чтения, но SQLITE_BUSY всё ещё возможен, если другой процесс удерживает блокировку записи.
BEGIN IMMEDIATE;-- Здесь выполняйте только ваши реальные SQL-запросы изменения данныхCOMMIT;#Способ 3. Найдите того, кто держит блокировку
- Проверьте, не запущен ли второй экземпляр приложения, фоновая задача или импорт, обращающиеся к тому же файлу.
- Убедитесь, что все курсоры закрыты, а транзакции завершены — в том числе при исключениях (используйте
try/finally). - На Linux посмотрите, какие процессы открыли файл базы.
fuser -v /path/to/app.sqlite#Способ 4. Организуйте одного писателя
Для импорта больших пакетов используйте очередь и один процесс записи, а публичные запросы оставьте только на чтение. Так сайт на SQLite стабильно работает под нагрузкой.
#Как проверить результат
- Нагрузочный тест (одновременные записи и чтения) проходит без
SQLITE_BUSY. PRAGMA journal_mode;возвращаетwal,PRAGMA busy_timeout;возвращает заданное значение.
#Если не помогло
Если база хранится на сетевом диске (NFS, SMB), перенесите её на локальный диск: блокировки SQLite на сетевых ФС ненадёжны. Если одновременных писателей действительно много, оцените переход на клиент-серверную СУБД (PostgreSQL, MySQL).