Реляційні бази даних: B+ дерева, SQL, транзакції та відповідність вимогам ACID

Одним із основних методів зберігання даних в додатках є використання баз даних. Серед численних типів, реляційні бази даних використовуються для організації даних у структуровані таблиці з чітко визначеними зв'язками. Ці бази даних керуються за допомогою спеціалізованої мови запитів, відомої як SQL (Structured Query Language). Системи, які поєднують реляційні бази даних з SQL, називаються Системами управління реляційними базами даних (RDBMS), і саме на них зосереджена ця стаття.

Механізм роботи

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

Що таке B+ дерево?

На відміну від бінарного дерева, де кожен вузол може мати максимум два нащадки, B+ дерево дозволяє кожному вузлу мати кілька нащадків (до M нащадків, де M — це "порядок" дерева).

B+ дерево складається з двох типів вузлів: внутрішні вузли та листи. Внутрішні вузли функціонують як провідники, містячи ключі (значення), які направляють до відповідного дочірнього вузла. Цей процес триває, поки не буде досягнуто листа, який містить фактичні дані або посилання на них. Однією з основних особливостей B+ дерева є те, що всі листи зв'язані між собою в відсортованому зв'язаному списку, що полегшує виконання запитів на діапазони шляхом послідовного проходження через них.

Ще одна перевага B+ дерева — це відсортований порядок: ключі в кожному вузлі зберігаються в порядку зростання, що дозволяє швидко визначити, куди рухатись далі для пошуку необхідних даних. Оскільки кожен вузол може зберігати кілька ключів (або значень), дерево залишається відносно коротким і збалансованим, що дозволяє підтримувати швидкий пошук, мінімізуючи кількість рівнів, які потрібно пройти.

Основні переваги B+ дерев:

  • Швидкі пошуки: Відсортовані ключі та ієрархічна структура дозволяють швидко знаходити цільові дані.
  • Ефективні запити на діапазон: Відсортована структура зв'язаних списків листів спрощує отримання діапазону значень і робить цей процес дуже ефективним.
  • Збалансованість і компактність: Можливість зберігати кілька ключів на вузол зберігає дерево збалансованим і зменшує його висоту, що призводить до меншої кількості рівнів, через які потрібно пройти під час пошуку.

pic

Таблиці

У реляційних базах даних дані організовані у таблиці, які визначаються за допомогою схеми. Схема вказує структуру таблиці, включаючи поля (стовпці) та їх типи даних. Наприклад, наступний запит створює таблицю під назвою Frog з трьома полями: PassportNumber, Name та Age.

CREATE TABLE Frog (  
 PassportNumber varchar(50),  
 Name varchar(100),  
 Age int  
);

У цій таблиці фактичні записи даних називаються рядками. Кожен рядок є окремим екземпляром даних, який відповідає структурі, визначеній схемою таблиці. Наприклад, жаба на ім'я ‘Jack’ віком 100 років буде рядком у таблиці Frog.

Зв'язки між таблицями

Множина таблиць може також мати зв'язки між собою. Наприклад, уявімо світ, де жаби можуть мати будинки. У такому випадку може бути створена окрема таблиця під назвою House з наступною схемою:

CREATE TABLE House (  
 OwnerPassportNumber varchar(50),  
 Address varchar(100)  
);

У цьому випадку можна встановити правило, яке вимагає, щоб кожен OwnerPassportNumber в таблиці House відповідав дійсному PassportNumber в таблиці Frog. Це гарантує, що кожен будинок має власника, який є існуючою жабою. У SQL таке правило називається обмеженням, а саме цей тип зв'язку реалізується за допомогою зовнішнього ключа, який встановлює зв'язок між двома таблицями.
У нашому прикладі поле OwnerPassportNumber в таблиці House буде виконувати роль зовнішнього ключа, гарантуючи, що воно завжди посилається на дійсний рядок у таблиці Frog.

Крім того, кожна таблиця потребує первинного ключа, щоб унікально ідентифікувати кожен рядок. Первинний ключ може складатися з одного або кількох стовпців, але він повинен гарантувати, що два рядки не мають однакове значення для цього ключа. У таблиці Frog стовпець PassportNumber є природним вибором для первинного ключа, оскільки кожна жаба повинна мати унікальний паспортний номер. Цей унікальний ідентифікатор дозволяє реляційним базам даних ефективно організовувати дані та встановлювати зв'язки між таблицями, як ми тільки що побачили.

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

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

SELECT Frog.PassportNumber, House.Address  
FROM Frog, House  
WHERE Frog.PassportNumber = House.OwnerPassportNumber;

У цьому запиті ми отримуємо дані з двох таблиць, об'єднуючи їх через спільне поле PassportNumber. Запит повертає номер паспорта та адресу жаби, яка володіє будинком. Хоча цей приклад трохи вигаданий, він слугує простим прикладом того, як зв'язки між таблицями можуть допомогти створювати складніші запити.

Транзакції в базах даних

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

  1. BEGIN: Ключове слово, яке позначає початок транзакції.
  2. Update: Модифікація даних певної жаби (наприклад, зміна її імені або віку).
  3. Insert: Додавання нової жаби до бази даних.
  4. Select: Отримання набору жаб за певними критеріями (наприклад, жаби старші за 50 років).
  5. COMMIT: Позначає успішне завершення транзакції, гарантуючи, що всі зміни будуть збережені назавжди.

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

pic

Властивості ACID

Більшість реляційних баз даних дотримуються властивостей ACID — атомарності, узгодженості, ізоляції та довговічності. Ці властивості гарантують, що реляційні бази даних зберігають цілісність даних навіть під час системних збоїв або одночасних операцій. Давайте пройдемося по кожній літері абревіатури.

pic

1. Атомарність (A)

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

  • Якщо транзакція включає оновлення даних жаби та вставку нового запису, обидві операції повинні бути успішними для того, щоб транзакція була завершена. Якщо одна з них не вдалася, жодна з змін не буде збережена.

2. Узгодженість (C)

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

  • Стовпець name в таблиці Frog може мати правило, що він не може містити значення NULL.
    Консистентність гарантує, що жодна транзакція не порушить це правило.

Приклади обмежень включають:

  • Унікальні значення для певних стовпців.
  • Забезпечення зв'язків зовнішнього ключа між таблицями.

3. Ізоляція (I)

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

Без належної ізоляції можуть виникнути проблеми, такі як брудні зчитування (dirty reads), фантомні зчитування (phantom reads) або неповторювані зчитування (non-repeatable reads). Наприклад:

  • Транзакція 1 збільшує вік жаби на ім'я Грег з 20 до 22.
  • Поки Транзакція 1 триває, Транзакція 2 збільшує вік Грега на 2.
  • Якщо Транзакція 1 не вдасться і буде відкотена, вік Грега повинен залишитися 20. Однак без ізоляції Транзакція 2 може спиратися на незавершене значення (22) і зберегти вік Грега як 24 замість 22. Це невідповідність називається брудним зчитуванням (dirty read).

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

4. Довговічність (D)

Довговічність гарантує, що після того, як транзакція буде підтверджена, її зміни будуть постійно збережені на диску. Це означає, що навіть якщо система зламається після того, як транзакція буде підтверджена, дані не будуть втрачені.

Наприклад:

  • Коли ви підтверджуєте транзакцію для оновлення віку жаби, зміна записується на диск і гарантовано зберігається — навіть якщо система бази даних відразу після цього зламається.

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

Ось і все! Я не люблю писати підсумки, якщо вони не потрібні, тому вибачте за різке завершення —

Перекладено з: Relational Databases: B+ Trees, SQL, Transactions, and ACID Compliance

Leave a Reply

Your email address will not be published. Required fields are marked *