Ответ
Транзакции в Django ORM обеспечивают атомарность операций с базой данных, гарантируя, что группа запросов будет выполнена целиком или не выполнена вовсе. Это критически важно для поддержания целостности данных.
По умолчанию Django работает в режиме autocommit, где каждый ORM-запрос выполняется в своей собственной транзакции. Для группировки нескольких операций в одну транзакцию используются следующие подходы:
1. Декоратор transaction.atomic
Самый простой способ обернуть всю функцию или метод в одну транзакцию. При возникновении исключения все изменения внутри функции будут автоматически отменены.
from django.db import transaction, IntegrityError
@transaction.atomic
def create_user_with_profile(username, first_name):
user = User.objects.create(username=username)
# Если здесь произойдет ошибка (например, IntegrityError),
# создание пользователя также будет отменено.
UserProfile.objects.create(user=user, first_name=first_name)
2. Контекстный менеджер transaction.atomic
Позволяет обернуть в транзакцию только определённый блок кода, а не всю функцию. Это даёт больше гибкости.
from django.db import transaction
def update_balance():
try:
with transaction.atomic():
# Блок кода, который должен быть атомарным
account_from = Account.objects.select_for_update().get(id=1)
account_to = Account.objects.select_for_update().get(id=2)
account_from.balance -= 100
account_to.balance += 100
account_from.save()
account_to.save()
except Account.DoesNotExist:
# Обработка ошибки, транзакция уже отменена
print("Счет не найден.")
Ключевые моменты:
@transaction.atomicиwith transaction.atomic()— основные и предпочтительные способы управления транзакциями.select_for_update()используется для пессимистичной блокировки строк на время транзакции, чтобы предотвратить их изменение другими процессами.- Вложенные блоки
atomicсоздают точки сохранения (savepoints), позволяя откатывать только часть операций.
Ответ 18+ 🔞
А, ну вот, подъехали к самому сокровенному — к тому, как не наебнуться с данными в Django. Смотри, тут всё просто, как три копейки, но если проебаться — будет пиздец, а не целостность.
По дефолту эта ORM ведёт себя как распиздяй: каждый запрос к базе — это отдельная, мелкая транзакция, которая тут же коммитится (autocommit). Это как покупать продукты по одной картошине, выходя из магазина после каждой. Хуёво, да? А если тебе надо купить ингредиенты для борща, и если хоть чего-то нет — то нахуй весь борщ? Вот для этого и нужны нормальные транзакции.
1. Декоратор @transaction.atomic — для ленивых и умных
Вешаешь волшебный декоратор на свою функцию — и вуаля, весь её код становится одним неделимым куском для базы. Всё выполнится, либо, в случае любой ебли, всё откатится, как будто ничего и не было. Красота.
from django.db import transaction, IntegrityError
@transaction.atomic
def create_user_with_profile(username, first_name):
user = User.objects.create(username=username)
# Если тут, блядь, вылезет IntegrityError (логин занят, например),
# то и юзер, которого только что создал, нахуй испарится. Чистота.
UserProfile.objects.create(user=user, first_name=first_name)
Представь, ты создал юзера, а потом профиль не встал из-за дурацкого условия. Без атомика у тебя в базе болтался бы полуфабрикат — юзер без профиля. А так — нихуя. Всё чисто.
2. Контекстный менеджер with transaction.atomic() — для точечных операций
А что, если тебе не всю функцию в транзакцию пихать, а только её сочный кусок? Вот тут и выручает контекстный менеджер. Зашёл в блок with — ты в транзакции. Вышел — либо всё ок, либо всё посралось.
from django.db import transaction
def update_balance():
try:
with transaction.atomic():
# Вот этот кусок кода — священная корова. Либо всё, либо ничего.
# Берём два счёта и нахуй их блокируем, чтобы никто другой не влез.
account_from = Account.objects.select_for_update().get(id=1)
account_to = Account.objects.select_for_update().get(id=2)
account_from.balance -= 100
account_to.balance += 100
account_from.save()
account_to.save()
except Account.DoesNotExist:
# Если счёта нет — транзакция уже откатилась сама, можно просто ругнуться.
print("Ёпта, счёт не найден. Деньги уплыли в никуда.")
На что тут смотреть, чтобы не обосраться:
@transaction.atomicиwith transaction.atomic()— твои лучшие друзья. Используй их, когда делаешь несколько связанных изменений. Не выёбывайся.select_for_update()— это, блядь, очень важно в таких сценариях, как перевод денег. Эта штука говорит базе: "Слушай, эти строки я сейчас буду трогать, залочь их нахуй, чтобы никакой другой процесс параллельно не проебал мою логику". Без этого возможны гонки, и деньги могут волшебным образом испариться или удвоиться. Пиздец, а не фича.- Если ты вложишь один
atomicв другой — это создаст точки сохранения (savepoints). Это как если в большой транзакции ты можешь откатить не всё, а только до какой-то контрольной точки. Удобно, но не усложняй без нужды.
Короче, суть в чём: хочешь быть уверен, что твои данные не превратятся в кашу — оборачивай логически связанные операции в atomic. И не забывай про блокировки, когда несколько процессов могут лезть в одни и те же записи. Иначе будет овердохуища веселья при отладке.