Урок 80. MySQL и Python

📁 Блок: Базы данных ⏱️ Время изучения: ~90 мин 🎯 Сложность: Средняя
#close #for #in

⚡ Кратко: что меняется

  • Старый подход: ручное создание соединения и курсора, ручной вызов close() в блоке finally.
  • Новый подход: контекстный менеджер with закрывает ресурсы автоматически, даже при ошибке.
  • SQL-инъекция: подстановка значений через f-string или конкатенацию опасна. Используй %s / %(name)s.

⚖️ Ручное управление ресурсами vs контекстный менеджер

Раньше код часто писали так: создаём соединение, курсор, выполняем запросы и вручную закрываем ресурсы. Это работает, но легко забыть close() или пропустить ошибку.

Старый подход

import pymysql

connection = pymysql.connect(
    host="localhost",
    user="root",
    password="yourpassword",
    database="hr"
)
cursor = connection.cursor()

try:
    cursor.execute("SELECT * FROM departments")
    for row in cursor:
        print(row)
finally:
    cursor.close()
    connection.close()

Недостатки:

  • Нужно не забыть закрыть и курсор, и соединение.
  • При нескольких точках выхода код разрастается.
  • Если внутри try произойдёт ошибка до finally, ресурсы всё равно закроются, но сам код громоздкий.

Новый подход

import pymysql

config = {
    "host": "localhost",
    "user": "root",
    "password": "yourpassword",
    "database": "hr"
}

with pymysql.connect(**config) as connection:
    with connection.cursor() as cursor:
        cursor.execute("SELECT * FROM departments")
        for row in cursor:
            print(row)

Преимущества:

  • Меньше кода и он чище.
  • Ресурсы закрываются автоматически при выходе из блока.
  • Даже при исключении close() будет вызван.

🛡️ SQL-инъекция: f-string vs параметризованный запрос

Подстановка пользовательского ввода в SQL через f-string или сложение строк делает программу уязвимой.

Опасный код

user_input = "1 OR 1=1"
sql = f"SELECT * FROM employees WHERE employee_id = {user_input}"
cursor.execute(sql)
# Выполнится: SELECT * FROM employees WHERE employee_id = 1 OR 1=1
# Злоумышленник получит все записи.

Безопасный код

user_input = input("Введите ID сотрудника: ")
cursor.execute(
    "SELECT * FROM employees WHERE employee_id = %s",
    (user_input,)
)
# Значение передаётся отдельно от SQL и безопасно экранируется.

📊 Сравнение в таблице

АспектСтарый подходНовый подход
Закрытие соединенияВручную connection.close()Автоматически через with
Закрытие курсораВручную cursor.close()Автоматически через with
Обработка ошибокТребует try ... finallyРаботает внутри with
Подстановка значенийf-string / конкатенация (опасно)Плейсхолдеры %s / %(name)s
ЧитаемостьБольше вспомогательного кодаКомпактный и понятный код
⚠️ Проверить по документации: в некоторых библиотеках и ORM (например, SQLAlchemy 2.x, psycopg) синтаксис плейсхолдеров отличается. В PostgreSQL обычно используется %s в psycopg2 или $1 в asyncpg. Всегда сверяйтесь с документацией конкретной библиотеки.