Авторский проект IT-специалиста Олега Барабанова Персональные публикации на тему IT и не только…

SQLite: восхитительная встраиваемая СУБД которая используется повсеместно

У многих опытных разработчиков есть такие примеры программных разработок, которые их вдохновляют с профессиональной точки зрения. Например для одних это проект Linux со всей его сложной историей развития и всего его комьюнити. Или знаменитый Doom который на каком только куске железа не запускали.

Но сегодня в своей статье я хочу поговорить об SQLite, как о проекте который я искренне считаю выдающимся. Для тех кто не знает SQLite — это компактная реляционная встраиваемая СУБД, которая используется в огромном количестве систем. Более подробнее про нее вы можете прочитать на официальном сайте sqlite.org или в той же Википедии.

У этой базы данных есть немало достоинств, которые я постараюсь описать своими словами.

Достоинства SQLite

Компактность

Особенность этой СУБД в том, что она поставляется фактически в виде одной подключаемой библиотеки. Достигается это за счет того, что весь исходный код собирается в единый sqlite.c , который затем компилируется в одну библиотеку. 

При этом в свою очередь SQLite является самодостаточной СУБД, не требующей сторонних зависимостей, обходясь возможностями стандартной библиотеки Си. Соответственно подключая к своему проекту SQLite вы не тяните автоматически какую-то еще цепочку зависимостей.

Я думаю эти преимущества прекрасно поймут те разработчики, которые сталкивались с ситуацией использования какого-нибудь сложного стороннего модуля, который тянет за собой в ваш проект кучу программного мусора, дополнительных конфигов, систем сборок и пр.

Легкость использования и минималистичность API

СУБД SQLite является не просто бессерверной встраиваемой базой данных, т.е. никакие сетевые соединения не используются и всё взаимодействие с ее API происходит через прямые вызовы ее библиотечных функций. А так как она поставляется в компактном виде, легко сделать врапперы для других языков программирования, не заморачиваясь с какими-нибудь особенностями систем сборок и пр. Фактически под каждый язык программирования в каком-либо виде есть готовая библиотека-обертка для работы с SQLite.

Ну и т.к. Sqlite написан на чистом Си, то благодаря ABI можно использовать механизмы FFI различных языков программирования, для вызова API этой библиотеки.

Надежность

SQLIte является очень надежной и тщательно тестируемой СУБД, соответствующей ACID. Как минимум стоит посмотреть на количество и качество части тестов, чтобы убедиться, что 100% покрытие тестами не шутки. При этом общее количество тестов намного большее за счет использование собственного набора тестов корпоративного уровня! 

Конечно надежность SQLite не отменяет необходимости в бэкапах, ибо есть множество сторонних факторов, которые могут привести к повреждению базы данных и которые разработчикам прикладных систем нужно учитывать. 

Легковесность

Поскольку это бессерверное решение то нет затрат ресурсов на какие-нибудь сетевые соединения, отдельные сервера и пр. Т.е. используется минимум накладных ресурсов, поскольку идет прямая работа с файлом базы данных.

При всем этом сама библиотека SQLite весит меньше мегабайта, при ее то функционале полноценной СУБД. Соответственно от присоединения этой библиотеки ваше приложение сильно не раздуется.

Расширяемость

Встраиваемость SQLite работает в обе стороны. Если каких-то встроенных фунций в SQLite вам не хватает, вы можете задать собственные пользовательские функции и использовать их в SQL запросах. Причем этот функционал реализован и в других языках программирования, например в PHP, JavaScript (NodeJS), Python и т.д.

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

Простота во всём

Достаточно библиотеку подключить к своему коду и СУБД уже можно использовать. Не надо никаких систем сборок, предварительного конфигурирования или каких-нибуль предварительных инициализаций. В большинстве стандартных случаев все прекрасно работает из коробки. Если необходимо задать какие-либо настройки, то они задаются через SQL запрос с использованием выражения PRAGMA.

Отличная и полная документация

У SQLite на сайте sqlite.org размещена прекрасная документация, которой в большинстве случае более чем достаточно. Там всё описано достаточно простым языком (хоть и только на английском), коротко, четко и ясно. При этом по мере новых релизов э ой СУБД, разработчиками документация обновляется своевременно.

Некоторые слабые стороны SQLite

Разумеется SQLite также имеет и свои слабые стороны, которые на мой взгляд обусловленны требованиями к встраиваемости, надежности, экономичности по ресурсам и пр. Давайте попробую некоторые моменты упомянуть, поскольку многие эти ограничения очевидны с т.з. логики.

Например SQLite плохо подходит для задач в которых идут частые конкурентные записи. Суть в том, что т.к. нет никакого сервера, то и нет какого-либо единого "контроллера", который бы разруливал очередь выполнения SQL команд. И для того чтобы избежать конкурентных запросов на внесение изменений в данные, SQLite полагается на банальную блокировку файла. Т.е. для того чтобы внести данные, SQLite открывает файл, блокирует его, вносит изменения и закрывает файл снимая также блокировку. Это не быстрый процесс, но для SQLite это по большей части безальтернативный подход.

Также SQLite для многих сложных запросов вполне может работать неоптимизированно. Поскольку SQLite бессерверная СУБД, у нее нет возможности фоново заранее проводить глубокий анализ БД, запросов или оптимальных стратегий поиска, а также оптимизировать хранение данных. Для этого надо самим время от времени использовать ANALYZE или явно запускать процесс оптимизации.

Тем не менее в большинстве задач можно добиться от SQLite очень высокой эффективности работы, но это если знать как эта СУБД работает, чтобы оптимально использовать ее сильные и слабые стороны.

Пример проблемы решаемой правильным пониманием работы SQLite

Есть один пример проблемы, на которую я часто вижу жалобы от разработчиков, которые не знают как работает SQLite. У разработчика стоит типичная задача импорта большого количества данных в базу данных и разработчик сталкивается с тем что весь импорт происходит довольно медленно. При этом импортирующий код типично состоит из кучи типичных DML операторов INSERT / UPDATE / DELETE / … :

/* Файл БД открывается, блокируется, делается запись, закрывается */;
INSERT INTO users (name) VALUES ('Oleg');
/* Файл БД открывается, блокируется, делается запись, закрывается */;
INSERT INTO users (name) VALUES ('Ivan');
/* Файл БД открывается, блокируется, делается запись, закрывается */;
INSERT INTO users (name) VALUES ('Alexander');
…

В этом примере простые INSERT выполняются поочередно и вроде бы больше ничего тут нет. Но тут есть подкол, что для каждого DML-оператора, если он выполняется не в открытой транзакции, создается неявная транзакция, которая при успешном завершении изменении, сразу же фиксируется, ну либо при ошибке (например памяти не хватило) делается откат. А транзакция в режиме записи сама по себе подразумевает блокировку файла, чтобы гарантировать эксклюзивный доступ на внесение изменений.

Фактически это можно было бы как-то так представить в несколько упрощенном виде:

BEGIN TRANSACTION; /* Файл открывается и блокируется */
INSERT INTO users (name) VALUES ('Oleg');
COMMIT; /* фиксируются изменения, файл закрывается и блокировка снимается */

BEGIN TRANSACTION; /* Файл открывается и блокируется */
INSERT INTO users (name) VALUES ('Ivan');
COMMIT; /* фиксируются изменения, файл закрывается и блокировка снимается */

BEGIN TRANSACTION; /* Файл открывается и блокируется */
INSERT INTO users (name) VALUES ('Alexander');
COMMIT; /* фиксируются изменения, файл закрывается и блокировка снимается */
…

Как видно, тут слишком много файловых операций для нашей задачи импорта кучи данных в БД. И все эти файловые операции достаточно медленные. И для того чтобы ускорить общую работу импорта данных, нам надо уменьшить количество файловых операций, для чего мы можем  просто завернуть в одну транзакцию сразу множество изменений.

BEGIN TRANSACTION; /* Файл открывается и блокируется */
INSERT INTO users (name) VALUES ('Oleg');
INSERT INTO users (name) VALUES ('Ivan');
INSERT INTO users (name) VALUES ('Alexander');
…
COMMIT; /* фиксируются изменения, файл закрывается, блокировка снимается */

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

Дополнительно к в примере стоит задать через PRAGMA journal_mode журналирование в оперативной памяти, чтобы в процессе транзакции избежать задействования медленного жесткого диска. Главное чтобы транзакции по размеру влезали в оперативную память, поэтому стоит разбить импорт на несколько транзакций.

В итоге даже при таком простом варианте скорость импорта в базу данных будет многократно выше по сравнению с первоначальным вариантом. Я надеюсь такого примера хватит чтобы показать что понимание работы SQLite позволяет очень эффективно использовать эту замечательную СУБД.

Применимость SQLite

Лично я уже достаточно давно в разработке использую SQLite. Например я использую эту СУБД для некоторых сайтов где использование больших клиент-серверных СУБД излишне и приводит к замедлению сайта поскольку соединений с сервером БД требует времени и ресурсов, а также контроля за лимитом соединений. И если сайт основное время занимается в основном только чтением из базы данных то SQLite на практике отлично себя показывает.

Вообще в документации SQLite прекрасно описано в каких случаях эта СУБД подходит, а в каких не очень. В общем применять SQLite нужно там где это уместно.

SQLite не просто СУБД, а серьезно организованный проект

Но вообще мне SQLite нравится не только как СУБД, а как серьезно организованный проект. Этот проект реально очень сложный и дело тут не в количестве строк кода. SQLite это не просто какая-то библиотека, она отвечает за сохранность данных, а я думаю не стоит обьяснять насколько фатальным является потеря важных данных. А ведь эта СУБД должна работать в различных автономных устройствах, с учетом ограниченных ресурсов и с поддержкой огромного зоопарка различных систем которые еще и архитектурно отличаются друг от друга. И во всех этих случаях она должна надежно обеспечивать целостность данных.

Эта библиотека уже сейчас реально используется в миллиардах устройств: на смартфонах, в Windows, MacOS и различных Linux дистрибутивах, используется в промышленных системах и в огромном количестве различных приложений.

Поэтому неудивительно что разработчики SQLite развивают весь этот проект консервативно и осторожно, с обязательным дотошным тестированием всего возможного функционала. Но помимо тестов стоит посмотреть и на сам код, который отлично документирован, аккуратен и легко читаем. Никакой толпы дженериков и кодогенерации, а на редкость чистый и понятный Си, с полным избеганием применения небезопасных операций в Си. Особенно на код SQLite приятно смотреть после того кода который обычно пишут embedded-разработчики для микроконтроллеров.

Еще стоит сказать пару слов о самих разработчиках SQLite. SQLite разрабатывается небольшой группой очень профессиональных программистов, во главе с создателем этой СУБД Ричардом Хиппом, который на мой взгляд является очень талантливым разработчиком. Можно найти множество интервью с ним, в которых он многое рассказывает о разработке SQLite и объясняет почему были сделаны те или иные архитектурные решения. Помимо SQLite он еще разработал очень интересную систему контроля версий Fossil, которая у них как раз используется для разработки SQLite и которая в свою очередь также использует SQLite.

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

В общем можно сказать что в проекте SQLite разработчики держат дисциплину и в коде и в тестировании и в организованности и в юридических аспектах и все выполняется с серьезным, но адекватным перфекционизмом. Это не просто какой-то кусок кода, это реально целый проект. Такую организованность нечасто можно встретить в открытых проектах.

Заключение

К данной СУБД те кто ее мало знают часто относятся с лёгким пренебрежением, типа это же "lite", практически игрушечная база данных. Но если немного покопаться в разработке этого проекта, то можно убедиться что на деле создание подобного проекта является крайне сложной задачей. Можно описать весь принцип разработки SQLite одной знаменитой фразой: "Делать сложное просто, а вот делать простое очень сложно".

В общем я могу много рассказывать о Sqlite. Не исключаю что когда-нибудь уделю время на написание статей с обсуждением способов эффективного использования этой СУБД, а также применимости её для различного рода задач. 

↑ ↓