Good

Вступ до баз даних

База даних — організоване сховище для зберігання, керування та отримання даних. Замість тисяч файлів — структурована система з швидким пошуком та надійністю.

💡 Чому це важливо для UMTC?
Всі сервіси UMTC використовують бази даних: Matrix Synapse зберігає повідомлення в PostgreSQL, Wiki використовує SQLite для пошуку, а моніторинг — для метрик.

Навіщо потрібна база даних?

flowchart LR
    subgraph without["❌ Без БД"]
        FILES["📁 JSON файли"]
        SLOW["Повільний пошук O(n)"]
        CONFLICT["Колізії записів"]
        LOSS["Втрата даних"]
    end

    subgraph with["✅ З базою даних"]
        DB["🗄️ PostgreSQL"]
        INDEX["Швидкий пошук O(log n)"]
        ACID["ACID транзакції"]
        BACKUP["Резервне копіювання"]
    end

    FILES --> SLOW --> CONFLICT --> LOSS
    DB --> INDEX --> ACID --> BACKUP

    style without fill:#fee2e2
    style with fill:#d1fae5

SQL vs NoSQL

SQL (реляційні бази даних)

Дані організовані в таблиці з рядками та колонками. Є чітка схема (структура даних).

flowchart TB
    subgraph users["Таблиця users"]
        U["id | name | email | created_at"]
    end

    subgraph orders["Таблиця orders"]
        O["id | user_id | product | price"]
    end

    users -->|"JOIN<br/>user_id"| orders

    style users fill:#dbeafe
    style orders fill:#d1fae5
id name email created_at
1 Олександр alex@example.com 2026-01-15
2 Марія maria@example.com 2026-01-16

Приклади SQL баз:
- PostgreSQL — найпотужніша, рекомендована
- MySQL/MariaDB — популярна для веб
- SQLite — вбудована, для простих застосунків

NoSQL (нереляційні бази даних)

Гнучка структура, без жорсткої схеми.

{
  "_id": "user_001",
  "name": "Олександр",
  "email": "alex@example.com",
  "orders": [
    { "product": "Laptop", "price": 25000 },
    { "product": "Mouse", "price": 500 }
  ],
  "preferences": {
    "theme": "dark",
    "language": "uk"
  }
}

Типи NoSQL:

Тип Опис Приклад
Document JSON-подібні документи MongoDB
Key-Value Простий ключ → значення Redis
Column Колонкове сховище Cassandra
Graph Зв'язки між даними Neo4j

Коли що використовувати?

mindmap
  root((Вибір БД))
    SQL PostgreSQL
      Структуровані дані
      Складні JOIN
      ACID транзакції
      Приклади
        Користувачі
        Замовлення
        Фінанси
    NoSQL MongoDB
      Гнучка схема
      Вкладені документи
      Горизонтальне масштабування
      Приклади
        Логи
        CMS контент
        IoT дані
    Redis
      Кешування
      Сесії
      Черги
      Rate limiting

Порівняльна таблиця

Критерій PostgreSQL MongoDB Redis
Тип SQL Document Key-Value
Схема Строга Гнучка Немає
Транзакції ACID Базові Обмежені
Масштабування Вертикальне Горизонтальне Горизонтальне
Швидкість запису Середня Висока Дуже висока
Складні запити Відмінно Добре Немає
Зберігання На диску На диску В RAM

ACID принципи

ACID — гарантії для транзакцій в SQL базах даних:

flowchart TB
    subgraph acid["ACID"]
        A["🔹 Atomicity<br/>Все або нічого"]
        C["🔹 Consistency<br/>Завжди коректний стан"]
        I["🔹 Isolation<br/>Транзакції незалежні"]
        D["🔹 Durability<br/>Дані не втрачаються"]
    end

    style acid fill:#dbeafe
Принцип Опис Приклад
Atomicity Транзакція виконується повністю або не виконується Переказ грошей: зняти + додати = атомарно
Consistency База завжди в коректному стані email унікальний — дублікати неможливі
Isolation Паралельні транзакції не заважають Два користувачі редагують різні рядки
Durability Збережені дані не втрачаються COMMIT пройшов — дані на диску

Приклад транзакції

-- Переказ грошей: атомарна операція
BEGIN;

UPDATE accounts SET balance = balance - 1000 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE user_id = 2;

-- Якщо все OK
COMMIT;

-- Якщо щось пішло не так
-- ROLLBACK;

ORM — що це?

ORM (Object-Relational Mapping) — бібліотека, що дозволяє працювати з базою через об'єкти замість SQL.

# Без ORM (чистий SQL)
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
row = cursor.fetchone()
user = {"id": row[0], "name": row[1], "email": row[2]}

# З ORM (SQLAlchemy)
user = session.query(User).filter(User.id == user_id).first()
print(user.name)  # Працюємо як з об'єктом

Популярні ORM

Мова ORM
Python SQLAlchemy, Django ORM
JavaScript Prisma, Sequelize, TypeORM
Go GORM, sqlx
Ruby ActiveRecord
PHP Eloquent (Laravel)

Плюси і мінуси ORM

✓ Простіший код
✓ Захист від SQL injection
✓ Переносимість між БД
✓ Міграції схеми

✗ Складні запити важче оптимізувати
✗ N+1 проблема
✗ Додатковий шар абстракції

Індекси

Індекс — структура даних для швидкого пошуку. Як індекс в книзі — замість гортати всі сторінки, дивишся на потрібну літеру.

flowchart LR
    subgraph without["❌ Без індексу O(n)"]
        direction LR
        R1["row 1"] --> R2["row 2"] --> R3["row 3"] --> RN["...1M рядків"]
    end

    subgraph with["✅ З індексом O(log n)"]
        direction TB
        M["[M]"]
        D["[D]"]
        S["[S]"]
        A["[A] ← alex@..."]

        M --> D
        M --> S
        D --> A
    end

    style without fill:#fee2e2
    style with fill:#d1fae5
-- Створення індексу
CREATE INDEX idx_users_email ON users(email);
💡 Коли створювати індекси?
- Колонки в WHERE - Колонки в JOIN - Колонки в ORDER BY - Унікальні значення (email, username)

Коли створювати індекси?

✓ Колонки в WHERE
✓ Колонки в JOIN
✓ Колонки в ORDER BY
✓ Унікальні значення (email, username)

✗ Не створювати на все поспіль
✗ Індекси займають місце
✗ Індекси уповільнюють INSERT/UPDATE

Практичний приклад: вибір БД

Веб-застосунок (типовий)

PostgreSQL — основна база
├── users (id, email, password_hash, created_at)
├── posts (id, user_id, content, created_at)
├── comments (id, post_id, user_id, content)
└── ...

Redis — кешування та сесії
├── session:abc123 → {user_id: 1, ...}
├── cache:posts:recent → [post1, post2, ...]
└── rate_limit:user:1 → 45

Docker Compose

services:
  app:
    image: myapp
    depends_on:
      - postgres
      - redis

  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:

Типові помилки

N+1 проблема

# Погано: N+1 запитів
users = User.query.all()              # 1 запит
for user in users:
    print(user.orders)                # N запитів (по одному на user)

# Добре: 1-2 запити
users = User.query.options(
    joinedload(User.orders)
).all()

Відсутність індексів

-- Повільно (Full Table Scan)
SELECT * FROM logs WHERE created_at > '2026-01-01';

-- Швидко (Index Scan)
CREATE INDEX idx_logs_created ON logs(created_at);
SELECT * FROM logs WHERE created_at > '2026-01-01';

SELECT *

-- Погано: тягнемо всі колонки
SELECT * FROM users;

-- Добре: тільки потрібні
SELECT id, name, email FROM users;

Пов'язані теми

Шлях: services/databases/intro.md

UMTC Wiki © 2026 | Ukrainian Military Tactical Communications