Перейти к основному содержимому

Встраиваемая база данных из С

Разработчику Архитектору

Встраиваемая база данных из С

Встраиваемая СУБД — библиотека, которая работает внутри вашей программы, без отдельного сервера. Классический пример — SQLite: один файл app.db на диске, вызовы функций из C вместо сетевого протокола.

Общие понятия SQL (таблицы, SELECT, INSERT) — в разделе SQL. Здесь — как это выглядит в коде на С.

Термины API SQLite

ИмяРоль
sqlite3«соединение» с базой (один файл)
sqlite3_stmtподготовленный запрос с плейсхолдерами ?
sqlite3_stepвыполнить шаг (одна строка результата или завершение)
sqlite3_bind_*подставить значение вместо ?
sqlite3_column_*прочитать столбец текущей строки
SQLITE_OK, SQLITE_ROW, SQLITE_DONEкоды результата

Модель «библиотека в процессе»

В отличие от клиент-серверной PostgreSQL или MySQL:

  • нет отдельного демона;
  • нет сетевого порта;
  • запросы выполняются вызовами функций API;
  • файл .db — обычный файл в файловой системе.

Приложение линкуется с библиотекой (часто libsqlite3) и включает заголовок с объявлениями API.


Жизненный цикл соединения

Типичная последовательность:

  1. Открыть базу: указатель на соединение (sqlite3 *db).
  2. Выполнить SQL — создание таблиц, вставка, выборка.
  3. Закрыть соединение — освободить ресурсы.
#include <sqlite3.h>

int rc = sqlite3_open("app.db", &db);
if (rc != SQLITE_OK) {
fprintf(stderr, "open failed: %s\n", sqlite3_errmsg(db));
return 1;
}

/* ... работа ... */

sqlite3_close(db);

sqlite3_open создаёт файл, если его нет (при успешных правах на каталог). Ошибки — кодами SQLITE_* и текстом sqlite3_errmsg(db).

Разбор проверки:

int rc = sqlite3_open("app.db", &db);
if (rc != SQLITE_OK) {
/* db может быть NULL или содержать сообщение */
fprintf(stderr, "%s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}

Любой путь, где open не удался, должен вызывать sqlite3_close, чтобы не утекали дескрипторы.


Выполнение SQL

Простой путь — одна строка SQL и callback для каждой строки результата (sqlite3_exec). Подходит для CREATE TABLE, миграций, редких административных команд.

Подготовленные запросы (sqlite3_prepare_v2 + sqlite3_step) — основной путь для повторяющихся операций:

const char *sql = "INSERT INTO users (name, age) VALUES (?, ?);";
sqlite3_stmt *stmt;

if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK)
goto fail;

sqlite3_bind_text(stmt, 1, "Alice", -1, SQLITE_TRANSIENT);
sqlite3_bind_int(stmt, 2, 30);

if (sqlite3_step(stmt) != SQLITE_DONE)
goto fail;

sqlite3_finalize(stmt);

Плейсхолдеры ? или :name защищают от SQL-инъекций и переиспользуют план запроса.

Цикл чтения:

while (sqlite3_step(stmt) == SQLITE_ROW) {
const char *name = (const char *)sqlite3_column_text(stmt, 0);
int age = sqlite3_column_int(stmt, 1);
}

Что возвращает sqlite3_step:

КодЗначение
SQLITE_ROWесть очередная строка результата — читайте column_*
SQLITE_DONEзапрос завершён (для INSERT/UPDATE без строк)
другоеошибка — смотрите sqlite3_errmsg

Индексы столбцов в column_* начинаются с 0. Строка из column_text действительна до следующего step на этом stmt — для долгого хранения скопируйте в свой буфер.

Плейсхолдеры: sqlite3_bind_text(stmt, 1, "Alice", -1, SQLITE_TRANSIENT) — номер 1 соответствует первому ? в SQL. Четвёртый аргумент -1 значит «длина по \0». SQLITE_TRANSIENT — SQLite скопирует строку сам.


Транзакции

По умолчанию SQLite может автоматически фиксировать каждую операцию. Для пакетной вставки оборачивают в транзакцию:

BEGIN;
/* множество INSERT */
COMMIT;

При сбое — ROLLBACK. В API это те же строки SQL через exec или отдельные функции транзакций. Пакет в одной транзакции на порядки быстрее тысяч одиночных commit.


Типичные ошибки

ПроблемаПричина
database is lockedдругой поток/процесс держит запись; нужен таймаут или очередь
утечка stmtзабыли sqlite3_finalize
гонка при потокаходно соединение на поток или режим serialized + мьютекс
несовпадение типов bindbind_int и bind_text для столбца

См. Многопоточность на С: разделяемое соединение без синхронизации недопустимо.


Схема и миграции

Таблицы создают один раз при старте или версионируют схему:

CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);

Номер версии схемы хранят в таблице pragma user_version или отдельной таблице миграций; при обновлении приложения выполняют ALTER / пересоздание по скрипту.


Когда встраиваемая БД уместна

Подходит: локальное хранилище приложения, прототип, офлайн-режим, встраиваемые устройства, тестовые стенды.

Слабее: высокая конкурентная запись с множества сетевых клиентов, сложная репликация, тяжёлая аналитика на кластере — там серверные СУБД из обзора баз данных.


Связь с идиомами С

  • Проверять каждый код возврата API.
  • Освобождать stmt на всех путях (очистка с goto).
  • Не хранить указатели на column_text после следующего step без копирования строки.

См. также: Файловый ввод-вывод, Справочник.


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).