Найти в Дзене

Как подружить Python и PostgreSQL. Часть 2: Серьезные отношения (Пул соединений и транзакции)

Привет, дорогой читатель! В прошлой части мы научились подключаться к базе и создали первую таблицу. Это было волнительно, но в реальной жизни все сложнее. Представьте, что вы написали крутой сайт на Django/FastAPI, запустили его, и тут пришли пользователи. Много пользователей. Если на каждый запрос к базе ваше приложение будет открывать новое соединение, база данных очень быстро скажет "Извините, я устало, больше не могу" и упадет . Знакомо? Сегодня поговорим о том, как этого избежать, и разберем две важные темы: пул соединений и транзакции. В коде из первой части мы открывали соединение, делали дело и закрывали. Для одного запуска — ок. Для веб-приложения с сотней одновременных запросов — катастрофа. Пул — это как клубный зал ожидания. Мы заранее открываем, скажем, 10 соединений и держим их "теплыми". Когда приложению нужно поговорить с базой, оно берет уже готовое соединение из пула, работает с ним и возвращает обратно. Никакой лишней суеты. Как это сделать с помощью psycopg2: im
Оглавление

Привет, дорогой читатель! В прошлой части мы научились подключаться к базе и создали первую таблицу. Это было волнительно, но в реальной жизни все сложнее.

Представьте, что вы написали крутой сайт на Django/FastAPI, запустили его, и тут пришли пользователи. Много пользователей. Если на каждый запрос к базе ваше приложение будет открывать новое соединение, база данных очень быстро скажет "Извините, я устало, больше не могу" и упадет . Знакомо? Сегодня поговорим о том, как этого избежать, и разберем две важные темы: пул соединений и транзакции.

Проблема: "Утечка соединений"

В коде из первой части мы открывали соединение, делали дело и закрывали. Для одного запуска — ок. Для веб-приложения с сотней одновременных запросов — катастрофа.

  1. Открытие нового соединения — штука медленная (требует рукопожатия и аутентификации).
  2. У PostgreSQL есть лимит на количество одновременных соединений (обычно 100). Если ваше приложение попытается открыть 101-е — база просто откажется работать .

Решение: Пул соединений

Пул — это как клубный зал ожидания. Мы заранее открываем, скажем, 10 соединений и держим их "теплыми". Когда приложению нужно поговорить с базой, оно берет уже готовое соединение из пула, работает с ним и возвращает обратно. Никакой лишней суеты.

Как это сделать с помощью psycopg2:

import psycopg2
from psycopg2 import pool

# Создаем пул соединений (минимум 1, максимум 20 соединений)
# DSN = Data Source Name — строка подключения
dsn = "postgresql://postgres:ваш_пароль@localhost:5432/my_blog_db"
connection_pool = psycopg2.pool.SimpleConnectionPool(1, 20, dsn)

def get_post_by_id(post_id):
"""Функция для получения поста из бд с использованием пула."""
conn = None
cursor = None
try:
# Берем соединение из пула
conn = connection_pool.getconn()
cursor = conn.cursor()

cursor.execute("SELECT * FROM posts WHERE id = %s;", (post_id,))
post = cursor.fetchone()
return post

except Exception as e:
print(f"Ошибка: {e}")
return None
finally:
# Возвращаем соединение в пул (но не закрываем его!)
if cursor:
cursor.close()
if conn:
connection_pool.putconn(conn)

# Используем
print(get_post_by_id(1))

Видите магию? Мы не создаем новые соединения, а переиспользуем старые. База данных скажет вам спасибо и прослужит дольше.

Атомарность и Транзакции

Теперь представьте, что мы переводим деньги с одного счета на другой. Это две операции:

  1. Снять 100 рублей со счета А.
  2. Положить 100 рублей на счет Б.

Что будет, если после первого запроса сервер выключится, а второй не выполнится? Деньги пропадут. В мире баз данных это называется "потеря целостности".

Чтобы такого не случилось, существуют транзакции. Это либо "выполнится всё", либо "не выполнится ничего". В psycopg2 транзакции управляются через commit() и rollback().

Вот как правильно переводить "деньги" (или лайки) с использованием транзакции и контекстного менеджера:

import psycopg2

conn_string = "postgresql://postgres:ваш_пароль@localhost:5432/my_blog_db"

def transfer_likes(from_user_id, to_user_id, amount):
conn = None
try:
conn = psycopg2.connect(conn_string)
# Отключаем autocommit, чтобы управлять транзакцией вручную
conn.autocommit = False

cursor = conn.cursor()

# 1. Уменьшаем баланс у первого пользователя
cursor.execute(
"UPDATE users SET likes = likes - %s WHERE id = %s;",
(amount, from_user_id)
)

# 2. Увеличиваем баланс у второго
cursor.execute(
"UPDATE users SET likes = %s + %s WHERE id = %s;",
(amount, to_user_id)
)

# Если всё хорошо — фиксируем изменения
conn.commit()
print("✅ Перевод выполнен успешно!")

except Exception as e:
print(f"❌ Ошибка перевода: {e}. Откатываем транзакцию...")
# Если что-то пошло не так — откатываем всё назад
if conn:
conn.rollback()
finally:
if conn:
conn.close()

# Пример вызова
# transfer_likes(1, 2, 10)

Обратите внимание: мы отключили autocommit. Теперь изменения станут постоянными только после вызова conn.commit(). Если ошибка случится между первым и вторым execute, сработает rollback(), и база данных вернется в исходное состояние .

Итог второй части

  • Пул соединений нужен, чтобы выдержать высокую нагрузку и не положить базу .
  • Транзакции нужны, чтобы данные оставались консистентными, даже если всё вокруг падает .

В следующей, заключительной части, мы замахнемся на святое — ORM. Поговорим о SQLAlchemy и о том, как не стрелять себе в ногу, работая с моделями.