В чём разница между реляционными и нереляционными базами данных?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между реляционными и нереляционными базами данных
Это один из самых важных выборов в архитектуре системы. За 10+ лет работы я использовал SQL (PostgreSQL, MySQL) и NoSQL (MongoDB, Redis, DynamoDB). Каждая категория решает разные проблемы.
Реляционные БД (SQL)
Концепция: Данные организованы в таблицы с рядами и колонками. Связи между таблицами через foreign keys. Схема фиксирована.
-- Таблица Users
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- Таблица Orders
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id), -- Foreign key
total DECIMAL(10, 2),
created_at TIMESTAMP DEFAULT NOW()
);
-- Таблица OrderItems
CREATE TABLE order_items (
id SERIAL PRIMARY KEY,
order_id INTEGER REFERENCES orders(id),
product_id INTEGER,
quantity INTEGER,
price DECIMAL(10, 2)
);
-- Сложный запрос (JOIN)
SELECT
u.name,
o.id,
SUM(oi.quantity * oi.price) as total
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_items oi ON o.id = oi.order_id
WHERE u.id = 123
GROUP BY u.id, o.id;
Характеристики:
RELATIONAL_DB_FEATURES = {
"schema": "Фиксированная структура (ACID гарантии)",
"relationships": "Foreign keys, JOIN'ы",
"consistency": "Strong consistency (ACID)",
"transactions": "ACID транзакции",
"scaling": "Вертикальное (один мощный сервер)",
"queries": "SQL (мощный язык запросов)",
"use_cases": "Финансы, учёт, структурированные данные",
}
ACID гарантии:
# Atomicity — всё или ничего
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1; # Снять 100
UPDATE accounts SET balance = balance + 100 WHERE id = 2; # Положить 100
COMMIT; # Либо обе операции прошли, либо ни одна
# Если произойдёт сбой между UPDATE'ами — откатываются обе
# Consistency — данные всегда согласованы
# Foreign keys гарантируют, что order_id всегда указывает на существующий заказ
# Isolation — разные транзакции не мешают друг другу
# Transaction 1: читает данные
# Transaction 2: меняет те же данные
# Transaction 1: видит consistent snapshot (или ждёт)
# Durability — сохранено на диск
# После COMMIT данные не потеряются даже при сбое сервера
Нереляционные БД (NoSQL)
Концепция: Гибкие структуры данных. Документы, key-value, графы, поколоны. Масштабируемость горизонтально.
1. Document БД (MongoDB)
from pymongo import MongoClient
client = MongoClient('mongodb://localhost:27017')
db = client['ecommerce']
# Нет схемы — каждый документ может быть разным
db.users.insert_one({
"_id": ObjectId(),
"name": "John",
"email": "john@example.com",
"orders": [ # Встроенная иерархия
{
"order_id": 1,
"total": 99.99,
"items": [
{"product_id": 10, "quantity": 2, "price": 49.99}
]
}
],
"preferences": { # Варьирующиеся поля
"theme": "dark",
"language": "ru"
},
"tags": ["vip", "early-adopter"] # Массивы
})
# Запрос
user = db.users.find_one({"email": "john@example.com"})
print(user["orders"][0]["total"]) # Доступ к nested данным
2. Key-Value БД (Redis)
import redis
r = redis.Redis(host='localhost', port=6379)
# Простые пары ключ-значение
r.set('user:123:name', 'John')
r.set('user:123:email', 'john@example.com')
# Hash
r.hset('user:123', mapping={
'name': 'John',
'email': 'john@example.com',
'age': 30
})
# List
r.rpush('notifications:user:123', 'You got a like', 'New message', 'System update')
# Set
r.sadd('followers:user:123', 'user:456', 'user:789')
# Sorted Set (для leaderboards, rankings)
r.zadd('leaderboard', {'user:1': 100, 'user:2': 85, 'user:3': 92})
rank = r.zrevrank('leaderboard', 'user:2') # 2nd place
# Very fast
value = r.get('user:123:name') # ~0.1ms
3. Wide-Column БД (Cassandra)
from cassandra.cluster import Cluster
cluster = Cluster(['127.0.0.1'])
session = cluster.connect('keyspace_name')
# Огромное количество колонок, но разреженные
query = """INSERT INTO users (
user_id,
name,
email,
phone,
age,
address,
preferences_theme,
preferences_language,
created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """
session.execute(query, (
123, 'John', 'john@example.com', '+1234567890', 30,
'123 Main St', 'dark', 'en', datetime.now()
))
4. Graph БД (Neo4j)
from neo4j import GraphDatabase
driver = GraphDatabase.driver('bolt://localhost:7687', auth=('neo4j', 'password'))
def create_relationships(session):
session.run("""
CREATE (u1:User {name: 'John'})
CREATE (u2:User {name: 'Jane'})
CREATE (p:Product {name: 'Laptop', price: 999})
CREATE (u1)-[:FOLLOWS]->(u2)
CREATE (u1)-[:PURCHASED]->(p)
CREATE (u2)-[:LIKES]->(p)
""")
with driver.session() as session:
# Найти рекомендации: "Продукты, которые нравятся людям, которых я слежу"
result = session.run("""
MATCH (me:User {name: 'John'})
MATCH (me)-[:FOLLOWS]->(friend)
MATCH (friend)-[:LIKES]->(product)
RETURN DISTINCT product.name, product.price
""")
for record in result:
print(f"Recommend: {record['product.name']} ${record['product.price']}")
Сравнение в таблице
| Параметр | SQL (PostgreSQL) | NoSQL (MongoDB) | Redis | Cassandra |
|---|---|---|---|---|
| Структура | Таблицы, фиксированная | Документы, гибкая | Key-value | Широкие колонки |
| Масштабируемость | Вертикальная | Горизонтальная | Горизонтальная | Горизонтальная |
| Консистентность | Strong (ACID) | Eventual | Eventual | Eventual |
| Скорость | ~5-50ms | ~10-100ms | ~1ms | ~10ms |
| Транзакции | Полные ACID | Ограниченные | Нет | Нет |
| Сложные запросы | JOIN, GROUP BY | Ограниченные | Нет | Нет |
| Размер данных | Петабайты | Петабайты | 256GB (обычно) | Петабайты |
| Готовность | Strong | Eventual | Eventual | Eventual |
Когда использовать что
SQL для:
# 1. Финансовые системы (ACID критичен)
class PaymentProcessor:
def transfer(self, from_id: int, to_id: int, amount: float):
# Гарантируем либо обе операции прошли, либо ни одна
with db.transaction():
db.execute("UPDATE accounts SET balance = balance - ? WHERE id = ?",
(amount, from_id))
db.execute("UPDATE accounts SET balance = balance + ? WHERE id = ?",
(amount, to_id))
# 2. Структурированные данные с отношениями
# Заказы → Пользователи → Адреса → Платежи (много связей)
# 3. Сложные запросы
query = """
SELECT c.name, COUNT(o.id) as orders, SUM(oi.price)
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
LEFT JOIN order_items oi ON o.id = oi.order_id
WHERE c.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY c.id
HAVING COUNT(o.id) > 5
ORDER BY SUM(oi.price) DESC
"""
NoSQL для:
# 1. Гибкие, развивающиеся структуры
db.events.insert_one({
"event_type": "user_login",
"user_id": 123,
"timestamp": datetime.now(),
"ip": "192.168.1.1",
# Новый тип события может иметь совсем другие поля
})
# 2. Масштабируемость горизонтальная
# MongoDB sharding на миллиарды документов
# 3. Real-time аналитика
redis.incr(f"page_views:today:{page_id}")
redis.zadd("trending_articles", {article_id: score})
# 4. Кэширование
class UserCache:
def get_user(self, user_id: int):
# Быстро из Redis
cached = redis.get(f"user:{user_id}")
if cached:
return json.loads(cached)
# Медленно из PostgreSQL
user = db.users.get(user_id)
redis.setex(f"user:{user_id}", 3600, json.dumps(user))
return user
Полиглот Persistence (Polyglot Persistence)
Современные системы часто используют несколько БД:
class ECommerceArchitecture:
def __init__(self):
# PostgreSQL — основное хранилище
self.postgres = PostgreSQL() # users, orders, payments
# MongoDB — гибкие данные
self.mongodb = MongoDB() # product_reviews, user_preferences
# Redis — кэш и очередь
self.redis = Redis() # session, cache, tasks
# Elasticsearch — поиск
self.elasticsearch = Elasticsearch() # full-text search
# Cassandra — time-series
self.cassandra = Cassandra() # metrics, logs
def create_order(self, user_id: int, items: list):
# 1. Сохраняем в PostgreSQL (ACID)
order = self.postgres.create_order(user_id, items)
# 2. Индексируем в Elasticsearch
self.elasticsearch.index_order(order)
# 3. Кэшируем в Redis
self.redis.set(f"order:{order.id}", order, ttl=3600)
# 4. Пишем метрику в Cassandra
self.cassandra.write_metric('orders.created', 1, timestamp=now())
return order
Мой опыт
Проект 1: только PostgreSQL
- Все структурировано
- Проблемы с масштабируемостью
- Медленные поиски
Проект 2: PostgreSQL + Redis
- PostgreSQL — source of truth
- Redis — кэш и сессии
- Намного быстрее
Проект 3: PostgreSQL + MongoDB + Redis + Elasticsearch
- PostgreSQL — user accounts, orders, payments
- MongoDB — flexible data (reviews, comments)
- Redis — real-time counters, cache
- Elasticsearch — product search
- Работает идеально
Практический выбор
START: PostgreSQL для всего (90% проектов это норма)
Затем добавляй:
- Redis если нужна скорость
- MongoDB если нужна гибкость
- Elasticsearch если нужна полнотекстовый поиск
- Cassandra если нужна временные ряды
Никогда не выбирай NoSQL только потому, что "это масштабируется". PostgreSQL масштабируется отлично, если правильно спроектировать. Сложность NoSQL часто не стоит потерянных гарантий ACID.
Вывод: SQL + PostgreSQL по умолчанию. NoSQL — когда есть конкретная причина.