🐛 Типичные ошибки и как их избежать
⚡ Кратко
Главные ловушки практикума: состояние гонки, забытый 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)