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,)) # Значение безопасно экранировано