🐛 Типичные ошибки

❌ Ошибка 1: SQL-инъекция через f-строки

# Неправильно
username = input("Enter username: ")
cursor.execute(f"SELECT id FROM users WHERE username = '{username}'")

Сообщение об ошибке: ошибки может не быть, но злоумышленник может ввести ' OR '1'='1 и получить доступ к чужим данным.

Почему возникает: пользовательский ввод напрямую подставляется в текст SQL-запроса, нарушая его структуру.

✅ Исправление

# Правильно
cursor.execute("SELECT id FROM users WHERE username = %s", (username,))

Что изменилось: значение передаётся вторым аргументом, и PyMySQL сам экранирует спецсимволы.

❌ Ошибка 2: Забытый connection.commit()

# Неправильно
cursor.execute("INSERT INTO users (username, password) VALUES (%s, %s)", (user, pwd))
# commit забыт!
print("Registration successful.")

Сообщение об ошибке: ошибки нет, но после переподключения данные отсутствуют.

Почему возникает: PyMySQL по умолчанию работает в режиме транзакций. Изменения сохраняются только после явного вызова commit().

✅ Исправление

# Правильно
cursor.execute("INSERT INTO users (username, password) VALUES (%s, %s)", (user, pwd))
connection.commit()
print("Registration successful.")

Что изменилось: после выполнения запроса вызывается commit(), чтобы зафиксировать изменения в базе.

❌ Ошибка 3: Не закрытый курсор

# Неправильно
cursor = connection.cursor()
cursor.execute("SELECT * FROM books")
# cursor не закрыт явно

Сообщение об ошибке: при большом числе запросов может закончиться пул курсоров или произойти утечка ресурсов.

Почему возникает: курсор занимает ресурсы сервера и клиента; незакрытые курсоры могут приводить к утечкам.

✅ Исправление

# Правильно
with connection.cursor() as cursor:
    cursor.execute("SELECT * FROM books")
    rows = cursor.fetchall()

Что изменилось: контекстный менеджер with автоматически закрывает курсор после использования.

❌ Ошибка 4: Путаница индексов в fetchone()

# Неправильно
result = cursor.fetchone()
book_id = result[0]
current_stock = result[2]  # ошибка: stock на самом деле под индексом 4

Сообщение об ошибке: IndexError: tuple index out of range или логическая ошибка с неверными данными.

Почему возникает: индексы в кортеже зависят от порядка столбцов в SELECT. Если запрос изменится, индексы перестанут соответствовать ожидаемым полям.

✅ Исправление

# Правильно: явно укажите нужные столбцы и распакуйте кортеж
cursor.execute("SELECT id, stock FROM books WHERE title = %s AND author = %s", (title, author))
result = cursor.fetchone()

if result is None:
    print("Book not found")
else:
    book_id, current_stock = result

Что изменилось: запрос выбирает только нужные столбцы в нужном порядке, а результат распаковывается по именам переменных. Также добавлена проверка на None.

❌ Ошибка 5: Неправильная обработка отсутствующей книги

# Неправильно
result = cursor.fetchone()
if result:  # иногда пишут if result is True, что неверно
    book_id, stock = result

Сообщение об ошибке: TypeError: cannot unpack non-iterable NoneType object.

Почему возникает: fetchone() возвращает None, если строка не найдена. Попытка распаковать None приводит к ошибке.

✅ Исправление

# Правильно
result = cursor.fetchone()
if result is None:
    # добавляем новую книгу
    pass
else:
    book_id, current_stock = result
    # обновляем количество

Что изменилось: перед распаковкой явно проверяем, что result не None.

❌ Ошибка 6: Забытый USE перед работой с таблицами

# Неправильно
cursor.execute("CREATE DATABASE IF NOT EXISTS bookstore")
cursor.execute("CREATE TABLE IF NOT EXISTS books (...)")  # таблица создаётся не в той базе!

Сообщение об ошибке: таблица создаётся в текущей базе данных по умолчанию, а не в bookstore.

Почему возникает: после создания базы нужно явно переключиться на неё командой USE.

✅ Исправление

# Правильно
cursor.execute("CREATE DATABASE IF NOT EXISTS bookstore")
cursor.execute("USE bookstore")
cursor.execute("CREATE TABLE IF NOT EXISTS books (...)")

Что изменилось: добавлена команда USE bookstore перед созданием таблиц.