Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni

🤖🚫 AI-free content. This post is 100% written by a human, as is everything on my blog. Enjoy!

Пости з тегом #SQLite

03.12.2024

Дев-адвент 3: SQLite як аналог Redux

Я звик думати про бази даних SQL як щось окреме, відносно далеке та чуже. Але застосування SQLite в останні дні спокушає передивитися ці підходи. Бо тут наче й база даних, але вона локальна та головне, власна.

А ще мені пощастило знайти цю бібліотеку GRDBQuery, яка інтегрує SQLite в компоненти SwiftUI та автоматично оновлює в них дані. Ну прямо як Redux в React!

Як вона це робить? А дуже просто: оскільки всі зміни бази тут відбувається через те ж підключення (це є обовʼязковою вимогою), то GRDBQuery спостерігає за змінами в окресленому “регіоні бази” - за замовчуванням це, здається, залучені в запиті таблиці — та перезапускає обʼєкт запиту для отримання нового значення.

Звісно, я б не зберігав буквально “стан застосунку” в SQLIte, бо хоч вона й швидка, але зміна значення в базі потребує більше бюрократії, ніж просто в памʼяті. Але для “змістовних даних” така модель повністю задовольняє, так що можна забути про всілякі View Model, довіритись “реактивному” оновленню та вантажити дані безпосередньо в компоненти.

(До речі: SQLite працює з файлами на диску, тобто база може бути дуже велика. Але робочу зону завантажує в памʼять, тож доки база маленька, або ми не звертаємось до старих даних — швидкість роботи буде “як памʼяттю”, а не “як з диском”.)

А далі — виходить, що SQL гарна мова для вибірки даних зі стану! Особливо, коли даних багато, а нам потрібні тільки їхня частина або підсумки. Причому не тільки можна перекласти логіку на SQL, а ще й впровадити індекси, чого в умовному Redux немає.

Цікаво, чи не використовують SQLite у “реактивному режимі” у всіляких Електронах, або навіть в вебзастосунках.


05.12.2024

Дев-адвент 5: SQLite - база з динамічною типізацією?

Нарешті мій внутрішній рубіст або джаваскриптівець може зрадіти: я натрапив на базу даних, в якій майже немає типів! Але… чи то гарно? Я ще не визначився.

Власне, в мене в табличці ключем є момент часу, та я хотів переконатися, що в SQLite, як і у звичному для мене PostgreSQL, таймстемпи зберігаються як цілі числа, та відповідно не гірше ніж числа на роль ключа.

⚠️ Сторінка про типи даних в SQLIte трохи спантеличує, якщо очікувати традиційної для БД моделі типів, та не читати першого речення, де сказано, що на відміну від всіх інших баз, в SQLite стовпчики не мають типу, а мають тільки схильність (affinity) до типу. Про це далі. У значень, втім, типи є.

🤔 Типів лише пʼять: ціле число, число з рухомою комою, текст, блоб та нуль. (Мало? Авжеж.) Та хоч в SQLite є підтримка дат, JSON, GIS, але все це зберігається в одному з цих типів. Дати — особливо дивно, бо функції дат приймають як рядок, так і число. Тобто ми легко можемо зберігати дати текстом та навіть не помітити.

🤯 А зі стовпчиками ще дивніше, бо команда CREATE TABLE дає можливість вказати як тип наче будь-яку назву. Яку ж “схильність” отримує стовпчик? Це (мені навіть дико це писати) визначається за змістом назви… наприклад, якщо в назві є підрядок INT, то буде схильність INTEGER. Що для мене важливо, DATETIME теж можна вказати типом, а схильність при цьому буде NUMERIC. Що наче гарно, але ні.

😱 Бо навіть зі схильністю NUMERIC, якщо писати в стовпчик рядок, то він буде конвертований в число тільки коли є числом. А якщо це рядок дати, то він рядком буде і збережений! І наче все буде працювати, тільки 1) з гіршою швидкістю та витратами памʼяті, 2) з загрозою дублікатів, якщо ми також писатимемо дати в чисельній формі, 3) з незрозумілим сортуванням. Я в шоці.

Підсумок: зробив дати чесним полем INTEGER, увімкнув режим STRICT, де тип значення повинний збігатися з типом стовпчика, та переналаштував кодування дат в клієнті. Порядок.


06.12.2024

Дев-адвент 6: внутрішня структура SQLite

Сьогодні часу не дуже багато було, тому задовольнився тим, що розібрався у внутрішній структурі SQLite, щоб зрозуміти її в контексті локальної бази даних, де сидітиме більшість даних застосунку. (До речі, в документації SQLite достатньо цікавих технічних деталей, щоб просто читати її для загального розвитку. Рекомендую.)

Щодо представлення записів (рядків таблиці): воно компактне, наскільки це можливо, та чимсь нагадує формати на кшталт msgpack. Для кожного значення відзначений свій тип, як памʼятаєте, але маркер типу складається з одного байту та навіть може містити в собі значення NULL, 0 та 1. Цілі числа зберігаються зі змінною шириною. В іншому, зрозумілий компактний формат, що інколи може бути важливо. Наприклад, JavaScript на ті ж дані витратить в рази більше памʼяті.

Щодо структури таблиць: тут все напрочуд просто та, на відміну від, наприклад, PostgreSQL, однозначно. Таблиця зберігається як Б-дерево. (Б-дерево це дерево, яке мінімізує свою висоту: тобто для БД, кількість вузлів-сторінок, за якими потрібно пройтися від кореня, щоб знайти запис.)

В кожного запису є ідентифікатор (rowid) - ціле число — та воно й використовується для побудови дерева. Цікаво, що якщо зробити ключем поле типу INTEGER PRIMARY KEY, то воно фактично й буде містити цей rowid, що пришвидшить пошук за ключем. (В іншому разі, спочатку потрібно знайти rowid за індексом, а потім вже запис за rowid.)

Індекси теж зберігаються як Б-дерева, з єдиною різницею, що тут вже значення в дереві можуть мати довільний тип, та це трохи ускладнює структуру даних. Індекси посилаються на rowid, тож в запитах, що поєднують декілька індексів, можливо фільтрувати записи ще за rowid , без завантаження.

Нарешті, в SQLite вся база даних міститься в одному файлі, він розбитий на сторінки (зазвичай по декілька КБ), та в памʼять можна завантажувати тільки ті, що потрібні. Що може стати дуже корисним, якщо даних багато, але ми не плануємо всі їх читати постійно.

Отже, що ми фактично отримуємо, порівняно зі збереженням даних в простих структурах в памʼяті? Прозорий спосіб заощадити на памʼяті (відвантаженням на диск), та також можливість швидко шукати з логарифмічною складністю — особливо за наявністю індексів.