Введение
SQLite принципиально отличается от PostgreSQL/MySQL (статьи 423-424) — это не отдельный серверный процесс, к которому приложение подключается по сети, а встраиваемая библиотека, работающая непосредственно в процессе самого приложения, с данными, хранящимися в единственном файле на диске. В статье разберём особенности SQLite и его практическое использование из Qt-приложений через QSqlDatabase, продолжая тему статьи 24 основного цикла (БД/ORM) и Raspberry Pi/embedded-циклов, где SQLite — частый, естественный выбор.
Концепция
SQLite не требует отдельного серверного процесса, конфигурации сети, или управления пользователями/правами доступа (статья 434) — вся БД представляет собой единственный файл, который приложение открывает напрямую, что делает SQLite идеальным выбором для desktop/мобильных приложений, встраиваемых систем (Raspberry Pi-цикл), и локального тестирования (статья 439), но менее подходящим для высоконагруженных, многопользовательских серверных приложений с интенсивной конкурентной записью (где SQLite использует блокировку на уровне всей БД для записи, что ограничивает параллелизм значительно сильнее, чем построчные блокировки PostgreSQL/MySQL InnoDB, статьи 409, 424).
Пример кода: SQLite
// Подключение к SQLite через QSqlDatabase в Qt-приложении
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
bool initializeDatabase(const QString &filePath)
{
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(filePath);
if (!db.open()) {
qWarning() << "Не удалось открыть БД:" << db.lastError().text();
return false;
}
QSqlQuery query;
query.exec("CREATE TABLE IF NOT EXISTS readings ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"device_id TEXT NOT NULL, "
"temperature REAL, "
"recorded_at DATETIME DEFAULT CURRENT_TIMESTAMP)");
return true;
}
// Параметризованный запрос через QSqlQuery — защита от SQL-инъекций (статья 433)
void insertReading(const QString &deviceId, double temperature)
{
QSqlQuery query;
query.prepare("INSERT INTO readings (device_id, temperature) VALUES (:device_id, :temperature)");
query.bindValue(":device_id", deviceId);
query.bindValue(":temperature", temperature);
if (!query.exec()) {
qWarning() << "Ошибка вставки:" << query.lastError().text();
}
}
// Включение WAL-режима (Write-Ahead Logging) — улучшает параллелизм чтения/записи SQLite
void enableWalMode(QSqlDatabase &db)
{
QSqlQuery query(db);
query.exec("PRAGMA journal_mode=WAL;");
// WAL позволяет читателям не блокироваться на время записи (в отличие от стандартного
// режима журналирования), что особенно полезно для приложений с конкурентным чтением/записью
}
Пояснения к коду
QSqlDatabase::addDatabase("QSQLITE") показывает, что с точки зрения Qt-приложения API для работы с SQLite почти не отличается от работы с PostgreSQL/MySQL через тот же QSqlDatabase — основное отличие лишь в драйвере ("QSQLITE" вместо "QPSQL"/"QMYSQL") и в setDatabaseName(), принимающем путь к файлу, а не имя БД на удалённом сервере. query.prepare()/bindValue() показывает параметризованный запрос (статья 433) — критично важная практика для защиты от SQL-инъекций, применимая одинаково независимо от конкретной используемой СУБД. PRAGMA journal_mode=WAL — специфичная для SQLite настройка, заметно улучшающая параллелизм между чтением и записью по сравнению со стандартным режимом журналирования, что особенно ценно для embedded-приложений (Raspberry Pi-цикл), где несколько потоков/процессов могут одновременно обращаться к одному файлу БД.
Подводные камни
- Использование SQLite для высоконагруженного, многопользовательского серверного приложения с интенсивной конкурентной записью, где более подходящим выбором были бы PostgreSQL/MySQL — блокировка на уровне всей БД при записи (даже с WAL-режимом, существенно улучшающим, но не полностью устраняющим это ограничение) делает SQLite принципиально менее подходящим для сценариев с большим количеством одновременных, конкурирующих операций записи от множества разных клиентов, в отличие от desktop/мобильных/embedded-приложений с единственным процессом, обращающимся к БД.
- Открытие одного файла SQLite БД одновременно несколькими отдельными процессами на платформах/файловых системах с проблемной поддержкой файловых блокировок (некоторые сетевые файловые системы, особенно при доступе с нескольких разных машин одновременно) — SQLite опирается на корректную работу файловых блокировок операционной системы для предотвращения повреждения данных при конкурентном доступе, и на файловых системах, где эти блокировки работают ненадёжно, конкурентный доступ к одному файлу SQLite может привести к повреждению данных.
- Забытое включение поддержки внешних ключей — в отличие от PostgreSQL/MySQL (с InnoDB), SQLite по умолчанию НЕ применяет ограничения внешних ключей (статья 397), даже если они явно объявлены в схеме, если не выполнена отдельная команда
PRAGMA foreign_keys = ON;для конкретного соединения — без этой явной настройки определения внешних ключей в схеме существуют лишь как документация, не давая реальной защиты ссылочной целостности. - Хранение файла БД SQLite в директории, синхронизируемой облачным сервисом (Dropbox, OneDrive, Google Drive и аналогичные) для desktop-приложения — синхронизация файла БД во время активной записи может привести к повреждению файла (синхронизация частично записанного, незавершённого файла) или конфликтам при попытке одновременного изменения с разных синхронизированных устройств; файл БД SQLite, активно используемый приложением, не должен находиться в директории такой автоматической синхронизации без явного понимания и принятия этого риска.