Menu

Частые ошибки SQLite: database is locked, readonly и другие

Разбираем ошибки SQLite, с которыми реально сталкиваешься в продакшене: database is locked, readonly database, malformed image и нарушения constraint — с готовыми решениями.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Ошибки SQLite — это просто способ что-то вам сообщить

Сообщения об ошибках SQLite короткие и порой загадочные, но за ними стоит вполне ограниченный набор реальных проблем. На практике почти всё, с чем вы столкнётесь в продакшене, укладывается в пять категорий: блокировки, права доступа, повреждение базы, расхождение схемы и нарушение ограничений. В этой статье разберём каждую — что её вызывает, что она на самом деле значит и как её починить.

К текстовым сообщениям прилагаются числовые коды (а расширенные коды — ещё точнее). В логах вы увидите обе формы:

Error: database is locked          -- код 5 (SQLITE_BUSY)
Error: unable to open database     -- код 14 (SQLITE_CANTOPEN)
Error: attempt to write a readonly -- код 8 (SQLITE_READONLY)
Error: database disk image is      -- код 11 (SQLITE_CORRUPT)

Если знаешь числовой код, искать причину гораздо проще — по запросу SQLITE_BUSY найдёшь куда больше полезного, чем по обычной английской фразе.

database is locked (SQLITE_BUSY)

Самая частая ошибка SQLite в любом приложении, где запись идёт из нескольких мест. SQLite сериализует запись: блокировку на запись в каждый момент держит только одно соединение. Если второй писатель не успевает получить блокировку за время busy timeout — получаешь именно эту ошибку.

Три способа решения, от самого действенного к менее:

WAL-режим сам по себе закрывает проблему блокировок для большинства сценариев. А busy_timeout — это страховка на случай реальной конкуренции при записи. Но дело не только в настройках: пройдитесь по коду. Если транзакция остаётся открытой, пока программа уходит в сетевой I/O, блокировка будет висеть всё это время. Держите транзакции короткими и делайте COMMIT (или ROLLBACK) сразу, как только работа закончена.

unable to open database file (SQLITE_CANTOPEN) — sqlite unable to open database file

SQLite попытался открыть файл, а ОС ответила отказом. В 95% случаев виноват путь к файлу или его директория:

-- Что нужно проверить:
-- 1. Существует ли путь?            ls -l /path/to/db.sqlite
-- 2. Существует ли родительский каталог? SQLite создаёт файл,
--    но не каталог, в котором он находится.
-- 3. Имеет ли пользователь, запускающий ваш процесс, права на чтение и запись
--    в каталоге (а не только в файле)?
-- 4. Смонтирован ли том, не переполнен ли он и не доступен ли только для чтения?

Тонкий момент: SQLite создаёт рядом с базой служебные файлы (-journal, -wal, -shm). Если сам файл доступен на запись, а каталог — нет, открытие проходит, а запись падает. Всегда давайте права на запись именно каталогу.

attempt to write a readonly database (SQLITE_READONLY)

Близкий родственник предыдущей ошибки sqlite. Файл открылся нормально, но запись не проходит. Причины по частоте:

  • У пользователя ОС нет прав на запись в файл или каталог с ним.
  • Соединение открыто в режиме «только чтение» (SQLITE_OPEN_READONLY или mode=ro в URI).
  • Том примонтирован read-only (типичная история с bind-mount в Docker и некоторыми облачными ФС).
  • База лежит на сетевой файловой системе, которая не поддерживает нужные SQLite блокировки.

Поправьте права доступа или перемонтируйте том. Если вы работаете в Docker, убедитесь, что bind mount не помечен как :ro и что директория принадлежит пользователю контейнера.

database disk image is malformed (SQLITE_CORRUPT)

Байты файла больше не соответствуют формату SQLite. Реальные причины почти всегда связаны с окружением: процесс был убит посреди записи на ФС без нормального fsync, базу копировали при активном писателе, отказало железо или файл синхронизировался через Dropbox/iCloud.

Для начала убедимся, что повреждение действительно есть:

Если integrity_check вернул ok, с самой базой всё в порядке — ошибка прилетела откуда-то ещё (чаще всего виновато подвисшее соединение). А вот если на выходе целый список проблем, придётся восстанавливать данные.

Самый чистый способ восстановления — команда .recover в CLI: она вытаскивает всё, что ещё можно прочитать, и переливает в новую базу:

sqlite3 corrupt.db ".recover" | sqlite3 recovered.db
sqlite3 recovered.db "PRAGMA integrity_check;"

Если у вас есть свежий бэкап — восстанавливайтесь из него, так быстрее и без двусмысленности в духе «вроде бы всё вытащили». О том, как правильно копировать живую базу, читайте на странице про резервное копирование и восстановление (подсказка: точно не через cp).

Ошибки no such table и no such column

Эти сообщения говорят ровно о том, о чём говорят, но причина обычно сводится к двум вариантам: вы подключились не к той базе, что думаете, либо не отработала миграция.

Проверьте строку подключения в вашем приложении: относительные пути разрешаются относительно текущей рабочей директории, а она отличается у терминала, IDE и продакшен-процесса. База в памяти (:memory:) каждый раз создаётся с нуля — на этом часто спотыкаются те, кто рассчитывает на сохранение данных.

С кавычками в идентификаторах та же история. Без кавычек имена нечувствительны к регистру, но "User" и "user" — это уже два разных идентификатора. Если таблица была создана с именем в кавычках, обращаться к ней придётся тоже только в кавычках.

Нарушения ограничений (constraint failed)

SQLite отклоняет запись, если она нарушает какое-либо ограничение. В тексте ошибки указано, какое именно:

За каждой такой ошибкой скрывается отдельный код: SQLITE_CONSTRAINT_UNIQUE, SQLITE_CONSTRAINT_CHECK, SQLITE_CONSTRAINT_NOTNULL. Чинить почти всегда нужно на уровне приложения — либо валидируйте данные до записи, либо осознанно обрабатывайте дубликаты через INSERT ... ON CONFLICT.

Отдельного разговора заслуживает FOREIGN KEY constraint failed: в SQLite внешние ключи по умолчанию отключены. Если их не включить, некорректные ссылки молча запишутся в базу, а вылезет это позже — когда вы наконец включите проверку. Задавайте этот pragma на каждом подключении:

cannot start a transaction within a transaction

Эта ошибка возникает, когда вы вызываете BEGIN, а транзакция уже открыта. SQLite не поддерживает вложенные транзакции, зато умеет работать с вложенными точками сохранения (savepoints) — а это, по сути, даёт тот же результат:

Если транзакциями управляет ваша ORM или фреймворк, скорее всего, вы запустили транзакцию дважды. Проверьте, включён ли autocommit, и не отдаёт ли пул соединений уже занятое соединение с открытой транзакцией.

disk I/O error (SQLITE_IOERR)

ОС отклонила операцию чтения или записи. Кончилось место на диске, моргнула сетевая ФС или кто-то удалил файл прямо из-под SQLite. Первым делом смотрим df -h. Вторым — где вообще лежит база: если это что-то ненадёжное вроде NFS или папки, синкающейся в облако, жди беды. SQLite рассчитывает на локальную POSIX-файловую систему с рабочим fsync. Если перенести базу некуда — смиритесь с тем, что риск повреждения данных выше.

syntax error near "..."

Парсер SQLite честно говорит, какой токен его смутил. Но чинить чаще всего нужно строки на три выше: пропущенная запятая, незакавыченный идентификатор, совпавший с ключевым словом, или строка с одинарными кавычками, которые забыли экранировать ('it''s', а не 'it's').

Используйте параметризованные запросы (плейсхолдеры ?) для пользовательского ввода вместо склейки SQL через конкатенацию строк — так вы разом избавитесь от целого класса синтаксических ошибок и SQL-инъекций.

Чек-лист для диагностики

Когда что-то падает в проде, такая последовательность шагов закрывает большинство случаев меньше чем за минуту:

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

Подводим итог курса

Вот и весь маршрут. Мы прошли путь от CREATE TABLE через джойны, индексы, транзакции, режим WAL, бэкапы — и закончили разбором ошибок sqlite, с которыми сталкиваешься в боевых условиях. Закономерности везде одни и те же: короткие транзакции, включённые внешние ключи, WAL-режим, регулярные бэкапы и здоровое уважение к PRAGMA integrity_check. Держитесь этих привычек — и SQLite будет тихо и незаметно работать у вас годами.

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

Почему SQLite выдаёт «database is locked»?

Другое соединение держит блокировку на запись, а ваше не дождалось своей очереди. Лечится тремя вещами: включить WAL-режим через PRAGMA journal_mode=WAL, чтобы читатели не блокировали писателей; увеличить таймаут — PRAGMA busy_timeout = 5000; и не забывать вовремя коммитить транзакции, а не держать их открытыми.

Как починить «attempt to write a readonly database» в SQLite?

В 99% случаев это проблема прав файловой системы, а не самого SQLite. У пользователя ОС, под которым запущен процесс, должны быть права на запись и в сам файл БД, и в каталог с ним — потому что SQLite создаёт рядом служебные файлы -journal или -wal. Проверьте владельца, права доступа и не смонтирован ли раздел в read-only.

Что означает «database disk image is malformed»?

SQLite прочитал из файла байты, которые не укладываются в ожидаемый формат — обычно это последствия повреждения: процесс убили во время записи, диск сбоит, или файл скопировали, пока БД была открыта. Сначала подтвердите диагноз через PRAGMA integrity_check, потом вытаскивайте уцелевшие данные командой .recover в CLI и сливайте их в новую базу. Если есть свежий бэкап — быстрее восстановиться из него.

Откуда берётся «no such table» или «no such column»?

Либо вы подключились не к тому файлу БД, который думаете, либо не прокатилась миграция. Посмотрите PRAGMA database_list — там видно, какой файл реально открыт, и .schema имя_таблицы, чтобы увидеть настоящие колонки. Ещё частая причина — опечатки и регистр в именах: для имён без кавычек SQLite регистронезависимый, а для закавыченных — уже нет.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ