← Назад к вопросам

Какие знаешь способы изоляции транзакций в PostgreSQL?

3.0 Senior🔥 161 комментариев
#Архитектура и паттерны#Базы данных (SQL)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Уровни изоляции транзакций в PostgreSQL

Изоляция транзакций — это ключевое понятие в ACID свойстве баз данных. PostgreSQL поддерживает четыре стандартных уровня изоляции SQL, каждый из которых защищает от различных проблем параллельного доступа.

1. READ UNCOMMITTED (Чтение незафиксированных данных)

Характеристика: Самый низкий уровень изоляции.

from django.db import transaction

with transaction.atomic():
    user = User.objects.get(id=1)

2. READ COMMITTED (Чтение зафиксированных данных)

Характеристика: Уровень по умолчанию в PostgreSQL. Транзакция видит только зафиксированные данные.

from django.db import transaction

@transaction.atomic(using='default', durable=False)
def transfer_money(account_from_id, account_to_id, amount):
    account_from = Account.objects.select_for_update().get(id=account_from_id)
    account_to = Account.objects.get(id=account_to_id)
    
    if account_from.balance >= amount:
        account_from.balance -= amount
        account_from.save()
        account_to.balance += amount
        account_to.save()

3. REPEATABLE READ (Повторяемое чтение)

Характеристика: Используется снимок состояния БД на начало транзакции. Все SELECT в транзакции видят одни и те же данные.

from django.db import transaction

@transaction.atomic(isolation=transaction.ISOLATION_SERIALIZABLE)
def update_with_repeatable_read():
    product = Product.objects.get(id=1)
    print(f'Первый запрос: {product.price}')
    product_again = Product.objects.get(id=1)
    print(f'Второй запрос: {product_again.price}')

4. SERIALIZABLE (Сериализуемость)

Характеристика: Самый высокий уровень. Транзакции выполняются так, будто идут последовательно, а не параллельно.

from django.db import transaction

@transaction.atomic(isolation=transaction.ISOLATION_SERIALIZABLE)
def count_products_and_check():
    count = Product.objects.count()
    new_product = Product.objects.create(name='New')

5. Django ORM + уровни изоляции

from django.db import transaction, connections

@transaction.atomic(isolation=transaction.ISOLATION_REPEATABLE_READ)
def safe_operation():
    order = Order.objects.select_for_update().get(id=1)
    order.status = 'processing'
    order.save()

with transaction.atomic(isolation=transaction.ISOLATION_SERIALIZABLE):
    pass

conn = connections['default']
with conn.cursor() as cursor:
    cursor.execute('SHOW transaction_isolation')
    print(cursor.fetchone())

6. SELECT FOR UPDATE — явная блокировка

from django.db import transaction

@transaction.atomic
def transfer_money_safe(account_from_id, account_to_id, amount):
    account_from = Account.objects.select_for_update(skip_locked=True).get(
        id=account_from_id
    )
    
    try:
        account_to = Account.objects.select_for_update(nowait=True).get(
            id=account_to_id
        )
    except transaction.TransactionManagementError:
        print('Строка заблокирована другой транзакцией')
    
    if account_from.balance >= amount:
        account_from.balance -= amount
        account_from.save()
        account_to.balance += amount
        account_to.save()

7. Явное управление уровнем в сыром SQL

from django.db import connection

def raw_transaction_with_isolation():
    with connection.cursor() as cursor:
        cursor.execute('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE')
        cursor.execute('BEGIN')
        try:
            cursor.execute('SELECT * FROM users WHERE id = %s', [1])
            user = cursor.fetchone()
            cursor.execute('UPDATE users SET active = true WHERE id = %s', [1])
            cursor.execute('COMMIT')
        except Exception as e:
            cursor.execute('ROLLBACK')
            raise

8. Retry логика для SERIALIZABLE

from django.db import transaction
import time

def retry_on_serialization_error(max_retries=3, delay=0.1):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    with transaction.atomic(isolation=transaction.ISOLATION_SERIALIZABLE):
                        return func(*args, **kwargs)
                except transaction.TransactionManagementError as e:
                    if 'serialization failed' in str(e) and attempt < max_retries - 1:
                        time.sleep(delay * (2 ** attempt))
                        continue
                    raise
        return wrapper
    return decorator

@retry_on_serialization_error()
def critical_operation():
    pass

Мои рекомендации на практике

  • По умолчанию: READ COMMITTED достаточно для 99% случаев
  • Когда нужна консистентность: REPEATABLE READ для отчётов и аналитики
  • Критичные операции: SERIALIZABLE + retry логика
  • Блокировки: SELECT FOR UPDATE для операций с деньгами и инвентарём
  • Избегай deadlock-ов: Всегда блокируй строки в одном порядке