⚖️ Старый vs Новый: выбор подхода

Доступ по индексу vs DictCursor

КритерийКортеж (по умолчанию)DictCursor
Формат строки(1, "Alice", "Smith", 5000){"id": 1, "first_name": "Alice", "last_name": "Smith", "salary": 5000}
Доступ к даннымПо индексу: row[1]По имени колонки: row["first_name"]
ЧитаемостьНиже — нужно помнить порядок колонокВысокая — имена говорят сами за себя
Устойчивость к изменениямЛомается, если изменится порядок колонок в SELECTРаботает при любом порядке колонок
Когда использоватьДля простых выборок с одной-двумя колонкамиДля удобной работы с именованными полями

Старый подход: доступ по индексу

import pymysql

config = {
    "host": "ich-db.edu.itcareerhub.de",
    "user": "ich1",
    "password": "password",
    "database": "hr",
}

connection = pymysql.connect(**config)
cursor = connection.cursor()
cursor.execute("SELECT first_name, last_name, salary FROM employees LIMIT 3")

for row in cursor:
    print(f"{row[0]} {row[1]} earns {row[2]}")

cursor.close()
connection.close()

Новый подход: DictCursor

import pymysql
from pymysql.cursors import DictCursor

config = {
    "host": "ich-db.edu.itcareerhub.de",
    "user": "ich1",
    "password": "password",
    "database": "hr",
    "cursorclass": DictCursor,
}

with pymysql.connect(**config) as connection:
    with connection.cursor() as cursor:
        cursor.execute("SELECT first_name, last_name, salary FROM employees LIMIT 3")
        for row in cursor:
            print(f"{row['first_name']} {row['last_name']} earns {row['salary']}")

Ручное закрытие ресурсов vs контекстный менеджер with

КритерийРучное управлениеwith
КодДлиннее — нужно явно вызывать close()Компактнее — ресурсы закрываются автоматически
Безопасность при ошибкахРиск утечки соединения, если забыть close() или произойдёт исключениеРесурсы закрываются даже при ошибке
ЧитаемостьСредняяВысокая
РекомендацияИспользовать только в учебных примерахПредпочтительный способ в реальном коде

Старый подход: ручное закрытие

import pymysql

config = {...}
connection = pymysql.connect(**config)
cursor = connection.cursor()

try:
    cursor.execute("SELECT * FROM employees")
    for row in cursor:
        print(row)
except pymysql.MySQLError as e:
    print("Error:", e)
finally:
    cursor.close()
    connection.close()

Новый подход: with

import pymysql

config = {...}

try:
    with pymysql.connect(**config) as connection:
        with connection.cursor() as cursor:
            cursor.execute("SELECT * FROM employees")
            for row in cursor:
                print(row)
except pymysql.MySQLError as e:
    print("Error:", e)

Автоматический commit vs явная транзакция с rollback()

КритерийБез транзакции / автоматический commitЯвная транзакция с rollback
КонтрольКаждый запрос применяется сразуНесколько запросов выполняются как единое целое
Безопасность при ошибкахЧасть изменений может сохраниться, часть — нетВсе изменения откатываются при ошибке
ИспользованиеПростые одиночные операцииСвязанные операции: перевод денег, покупка товара
СложностьНижеНемного выше, но надёжнее

Старый подход: пошаговые изменения без защиты

cursor.execute("UPDATE products SET stock = stock - 1 WHERE id = %s", (product_id,))
cursor.execute("UPDATE customers SET balance = balance - %s WHERE id = %s", (price, customer_id,))
cursor.execute("INSERT INTO purchases ...", (customer_id, product_id))
connection.commit()

# Если второй запрос упадёт, первый уже изменит базу — данные станут несогласованными.

Новый подход: транзакция с rollback

try:
    cursor.execute("UPDATE products SET stock = stock - 1 WHERE id = %s", (product_id,))
    cursor.execute("UPDATE customers SET balance = balance - %s WHERE id = %s", (price, customer_id,))
    cursor.execute("INSERT INTO purchases ...", (customer_id, product_id))
    connection.commit()
except Exception as e:
    connection.rollback()
    print("Transaction failed:", e)

Конкатенация строк SQL vs параметризованные запросы

КритерийКонкатенация строкПараметризованные запросы
БезопасностьУязвимость к SQL-инъекциямЗащита от SQL-инъекций
ЧитаемостьСредняяВысокая
Экранирование типовВручнуюАвтоматически
РекомендацияНикогда не использовать с пользовательским вводомЕдинственный правильный способ

Старый подход: опасная конкатенация

user_input = "1 OR 1=1"
sql = f"SELECT * FROM employees WHERE employee_id = {user_input}"
cursor.execute(sql)  # Выполнит SELECT для всех строк

Новый подход: параметризованный запрос

user_input = "1 OR 1=1"  # попытка инъекции
sql = "SELECT * FROM employees WHERE employee_id = %s"
cursor.execute(sql, (user_input,))  # Значение безопасно экранировано