← Назад к вопросам

Какие недостатки есть у NoSQL решений?

2.0 Middle🔥 111 комментариев
#Базы данных (NoSQL)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Недостатки NoSQL решений

Несмотря на преимущества NoSQL (масштабируемость, гибкость схемы), эти базы имеют значительные ограничения, которые нужно учитывать при выборе.

1. Отсутствие ACID гарантий

Базовая проблема

# Сценарий: перевод денег между счётами
# SQL (ACID) гарантирует оба операции или ни одна
# NoSQL может выполнить одну и "забыть" про вторую

from pymongo import MongoClient

client = MongoClient('mongodb://localhost:27017/')
db = client['bank']

# Попытка атомарного обновления
from_account = db.accounts.find_one({'_id': 1})
to_account = db.accounts.find_one({'_id': 2})

if from_account['balance'] >= 100:
    db.accounts.update_one({'_id': 1}, {'$inc': {'balance': -100}})
    # СБОЙ ЗДЕСЬ! Перезагрузка, ошибка или потеря соединения
    db.accounts.update_one({'_id': 2}, {'$inc': {'balance': 100}})  # Может не выполниться

MongoDB Transactions (попытка решения)

# MongoDB 4.0+ поддерживает транзакции, но с ограничениями
from pymongo import MongoClient

client = MongoClient('mongodb://localhost:27017/')
with client.start_session() as session:
    with session.start_transaction():
        db.accounts.update_one(
            {'_id': 1}, 
            {'$inc': {'balance': -100}},
            session=session
        )
        db.accounts.update_one(
            {'_id': 2}, 
            {'$inc': {'balance': 100}},
            session=session
        )
        # Коммитится на конец блока

Проблемы:

  • Работает только в Replica Sets
  • Дороговато по производительности
  • Не поддерживает все операции
  • Таймауты при сетевых сбоях

2. Отсутствие JOIN-ов и нормализации

Денормализованные данные

# Нужно хранить автора в каждом посте
from pymongo import MongoClient

db = MongoClient('mongodb://localhost:27017/')['blog']

# Храним данные автора в посте
db.posts.insert_one({
    '_id': 1,
    'title': 'Python tips',
    'content': '...',
    'author': {
        '_id': 101,
        'name': 'John',
        'email': 'john@example.com',
        'bio': 'Python developer'
    }
})

Проблема: дублирование данных

# Если изменить bio автора, нужно обновить ВСЕ посты
# Сценарий: автор изменил email

author_id = 101
new_email = 'newemail@example.com'

# Нужно обновить во ВСЕХ документах
db.posts.update_many(
    {'author._id': author_id},
    {'$set': {'author.email': new_email}}
)

# Если забыть одну коллекцию - data inconsistency!
db.comments.update_many(
    {'author._id': author_id},
    {'$set': {'author.email': new_email}}
)

# А может быть ещё 5 коллекций с авторами

SQL решает это просто

-- SQL: одно обновление в одной таблице
UPDATE users SET email = 'newemail@example.com' WHERE id = 101;

-- Все связанные данные получают новый email через FOREIGN KEY

3. Отсутствие сложных запросов

Простой фильтр в NoSQL

# Найти все заказы, где
# - статус = 'completed'
# - дата >= 2024-01-01
# - сумма > 100
# - товар в категориях ['electronics', 'books']

from pymongo import MongoClient

db = MongoClient()['ecommerce']

# MongoDB агрегация (сложно и медленно)
results = db.orders.aggregate([
    {'$match': {
        'status': 'completed',
        'created_at': {'$gte': datetime(2024, 1, 1)},
        'total': {'$gt': 100},
        'items.category': {'$in': ['electronics', 'books']}
    }},
    {'$group': {
        '_id': '$customer_id',
        'total_spent': {'$sum': '$total'},
        'order_count': {'$sum': 1}
    }},
    {'$sort': {'total_spent': -1}},
    {'$limit': 10}
])

То же самое в SQL (просто и читаемо)

SELECT 
    customer_id,
    SUM(total) as total_spent,
    COUNT(*) as order_count
FROM orders
WHERE 
    status = 'completed'
    AND created_at >= '2024-01-01'
    AND total > 100
    AND items.category IN ('electronics', 'books')
GROUP BY customer_id
ORDER BY total_spent DESC
LIMIT 10;

4. Слабая поддержка консистентности

Eventual Consistency проблемы

# Сценарий: кэширующий уровень
# Данные могут быть несогласованы между репликами

from redis import Redis
from pymongo import MongoClient

redis = Redis()
mongo = MongoClient()['mydb']

def get_user(user_id):
    # Проверяем кэш
    cached = redis.get(f'user:{user_id}')
    if cached:
        return json.loads(cached)
    
    # Получаем из MongoDB
    user = mongo.users.find_one({'_id': user_id})
    redis.setex(f'user:{user_id}', 3600, json.dumps(user))
    return user

def update_user(user_id, data):
    mongo.users.update_one({'_id': user_id}, {'$set': data})
    # БАГ: забыли очистить кэш!
    # redis.delete(f'user:{user_id}')
    # Теперь старые данные в кэше

Cache invalidation problem

# "There are only two hard things in Computer Science:
#  cache invalidation and naming things." — Phil Karlton

# Нужно синхронизировать несколько уровней вручную
def update_post(post_id, data):
    # Обновляем MongoDB
    db.posts.update_one({'_id': post_id}, {'$set': data})
    
    # Обновляем кэш
    redis.delete(f'post:{post_id}')
    
    # Очищаем кэш листов
    redis.delete(f'posts:recent')
    redis.delete(f'posts:trending')
    
    # Инвалидируем Elasticsearch
    es.update(index='posts', id=post_id, body={'doc': data})
    
    # Если забыть хотя бы один — data inconsistency

5. Отсутствие индексирования и оптимизатора запросов

N+1 queries problem

from pymongo import MongoClient

db = MongoClient()['blog']

# Плохо: N+1 запросов
posts = db.posts.find({'user_id': 123}).limit(10)
for post in posts:
    author = db.users.find_one({'_id': post['author_id']})  # +1 запрос!
    print(f"{post['title']} by {author['name']}")

# 1 запрос для постов + 10 запросов для авторов = 11 запросов!

Попытка решения через денормализацию

# Сохраняем данные автора в посте
db.posts.insert_one({
    '_id': 1,
    'title': 'Hello',
    'author': {
        '_id': 101,
        'name': 'John',
        'email': 'john@example.com'
    }
})

# Но это создаёт проблему с обновлением (см. выше)
# Нет идеального решения

6. Нет foreign keys и referential integrity

Orphaned documents

from pymongo import MongoClient

db = MongoClient()['mydb']

# Удаляем пользователя
db.users.delete_one({'_id': 101})

# Но его посты остаются в БД (orphaned)
posts = db.posts.find({'user_id': 101})
print(f"Orphaned posts: {posts.count_documents({})}")

# SQL автоматически обработал бы это
# DELETE FROM users WHERE id = 101;
# -- ON DELETE CASCADE удалил бы все посты

Нужно писать код вручную

def delete_user(user_id):
    #删除所有依赖于用户的数据
    db.posts.delete_many({'user_id': user_id})
    db.comments.delete_many({'user_id': user_id})
    db.likes.delete_many({'user_id': user_id})
    db.followers.delete_many({'user_id': user_id})
    db.follows.delete_many({'following_id': user_id})
    
    # И только потом удаляем самого пользователя
    db.users.delete_one({'_id': user_id})
    
    # Если забыть одну коллекцию - inconsistency!

7. Сложность с миграциями схемы

Гибкость схемы = хаос

# День 1: добавляем поле
db.users.insert_one({
    '_id': 1,
    'name': 'John',
    'age': 30
})

# День 2: решаем добавить дату рождения
db.users.insert_one({
    '_id': 2,
    'name': 'Jane',
    'age': 25,
    'birth_date': datetime(1999, 5, 10)
})

# День 3: рефакторим, переименовываем 'age' в 'years_old'
db.users.insert_one({
    '_id': 3,
    'name': 'Bob',
    'years_old': 35,
    'birth_date': datetime(1989, 3, 15)
})

# Теперь в БД смешанная схема!
def get_age(user_id):
    user = db.users.find_one({'_id': user_id})
    # Какое поле использовать? age или years_old?
    return user.get('age') or user.get('years_old')

8. Масштабирование вручную (sharding complexity)

MongoDB sharding

# Нужно вручную спланировать shard key
from pymongo import MongoClient

db = MongoClient('mongodb://localhost:27017/')['mydb']

# Плохой shard key - неравномерное распределение
db.command('shardCollection', 'mydb.users', key={'country': 1})

# Все пользователи из США будут на одном шарде = bottleneck!

# Хороший shard key - равномерное распределение
db.command('shardCollection', 'mydb.users', key={'_id': 1})

# Но вы не можете запросить всех пользователей из USA эффективно
# Нужно обратиться ко ВСЕМ шардам

9. Высокое потребление памяти

Document-based overhead

# Каждый документ хранит имена полей
{
    "first_name": "John",
    "last_name": "Doe",
    "email": "john@example.com",
    "phone": "555-1234"
}

# Vs SQL (только значения)
# John, Doe, john@example.com, 555-1234

# С миллионами документов разница становится существенной

Когда NoSQL хороша?

# 1. Большие объёмы неструктурированных данных
db.logs.insert_one({'message': '...', 'timestamp': ..., 'extra': {...}})

# 2. Real-time аналитика
db.metrics.insert_many([{...} for _ in range(1000000)])  # Fast

# 3. Кэширование (Redis)
redis.set('key', 'value', ex=3600)

# 4. Гибкая схема (прототипирование)
db.experiments.insert_one({...})  # Можно любые поля

Вывод

NoSQL не является панацеей. Её недостатки реальны и серьёзны:

  • Отсутствие гарантий
  • Сложная логика консистентности
  • Много ручной работы

Выбирай NoSQL только если:

  1. Масштаб требует горизонтального масштабирования
  2. Схема кардинально изменяется
  3. Реально нужна высокая скорость написания

Для большинства бизнес-приложений реляционные БД остаются лучшим выбором.

Какие недостатки есть у NoSQL решений? | PrepBro