🐛 Типичные ошибки и как их избежать

📁 Блок: Практикум / Базы данных ⏱️ Время изучения: ~120 мин 🎯 Сложность: Средняя
#lower #find #sort

⚡ Кратко

Главные ловушки практикума: состояние гонки, забытый commit, путаница ID, неатомарное обновление и мутация общей коллекции.

🐛 Ошибка 1. Состояние гонки при чтении баланса

Если два пользователя одновременно читают баланс и делают покупку, один из них может уйти в минус. В учебном проекте это редко, но в реальном приложении нужны транзакции с уровнем изоляции или обновление баланса атомарным запросом:

# Плохо: читаем баланс, потом обновляем
balance = cursor.execute("SELECT balance FROM users WHERE id = %s", (user_id,)).fetchone()[0]
if total_price <= balance:
    cursor.execute("UPDATE users SET balance = balance - %s WHERE id = %s", (total_price, user_id))

# Лучше: проверяем и обновляем в одном запросе
cursor.execute(
    "UPDATE users SET balance = balance - %s WHERE id = %s AND balance >= %s",
    (total_price, user_id, total_price),
)
if cursor.rowcount == 0:
    print("Insufficient funds.")

🐛 Ошибка 2. Забытый commit()

После UPDATE, INSERT и DELETE в PyMySQL изменения остаются в рамках транзакции, пока не вызван connection.commit(). Без него данные не сохранятся в базе.

⚠️ Важно: всегда вызывайте connection.commit() после успешных изменений. Иначе покупка исчезнет после закрытия соединения.

🐛 Ошибка 3. Путаница между user_id и book_id

В таблице purchases четыре поля, и легко перепутать порядок аргументов:

# Неправильно: book_id и user_id поменяны местами
cursor.execute("""
    INSERT INTO purchases (user_id, book_id, quantity, purchase_date)
    VALUES (%s, %s, %s, CURDATE())
""", (book_id, user_id, quantity))  # ← ошибка!

Проверяйте порядок полей и кортежа параметров. В реальных проектах помогают именованные параметры или ORM.

🐛 Ошибка 4. Неатомарное обновление

Если между проверкой остатка и обновлением таблицы books другой клиент купит последнюю книгу, остаток станет отрицательным. Решение — проверять условие прямо в UPDATE:

cursor.execute(
    "UPDATE books SET stock = stock - %s WHERE id = %s AND stock >= %s",
    (quantity, book_id, quantity),
)
if cursor.rowcount == 0:
    print("Not enough books in stock.")

🐛 Ошибка 5. Мутация shared collection

Если модуль создаёт глобальную коллекцию MongoDB на уровне импорта, а затем тесты или другие части программы изменяют её, можно получить непредсказуемое поведение. Храните коллекцию в одном месте и передавайте явно, либо используйте функцию-фабрику:

# Лучше: функция возвращает коллекцию
def get_search_collection():
    client = MongoClient(...)
    return client["ich_edit"]["bookstore_logs_searches"]

🐛 Ошибка 6. Пароль в коде

Учебные примеры часто содержат строку подключения с паролем. Не копируйте такой код в публичный репозиторий. Используйте os.environ или .env:

import os

uri = os.environ.get("MONGO_URI")
client = MongoClient(uri)