stepank
@stepank

ORM и изменения объекта в разных местах кода?

Допустим, есть некоторая система с БД. В этой системе есть три сущности:

— пользователь (User),

— транзакция (Transaction),

— счет пользователя (Account).



Связаны они так, что пользователь и транзакция ссылаются на счет по внешнему ключу.



Работать с этим всем делом мы хотим через какой-нибудь ORM. В моем случае — это Django ORM.



Далее, пусть у нас есть некоторый счет, на котором изначально баланс равен 100. Также у нас есть транзакция (transaction) и пользователь (user), которые ссылаются на этот счет.



Теперь делаем следующее:



1. Обращаемся к полю user.account, чтобы объект счета вытянулся из БД в одну переменную:



print user.account



2. Обращаемся к полю transaction.account, чтобы объект счета вытянулся из БД в другую переменную:



print transaction.account



3. Обновляем счет у пользователя и сохраняем:



user.account.balance = 0<br/>
user.account.save()




4. Обновляем счет у транзакции (но уже по-другому) и тоже сохраняем:



transaction.account.balance += 100<br/>
transaction.account.save()




В итоге в БД хранится информация о том, что баланс равен 200, т.к. в пункте 4 ничего не известно о том, что произошло в пункте 3. И, хотя такой результат вполне логичен, он может быть весьма неожиданным.



Собственно, ситуация вполне реальная: пункты 1 и 2 могут находиться в разных частях системы и ничего друг о друге не знают.



В связи с чем есть пара вопросов:

1. Как избежать подобных ошибок, кроме как быть очень аккуратным?

2. Может быть, это проблема чисто Django ORM, как дела с Java Hibernate, например?



PS: нашел похожее обсуждение на stackoverflow: stackoverflow.com/questions/680320/django-orm-cach...
  • Вопрос задан
  • 3119 просмотров
Пригласить эксперта
Ответы на вопрос 6
alexeygrigorev
@alexeygrigorev
Переворачиватель пингвинов
Я так понимаю, весь код нужно выполнять в пределах одной транзакции. В джанго это осуществляется с помощью декоратора transaction (from django.db import transaction). И операция 3 не сможет быть выполнения пока, скажем, операция 4 не будет завершена.
Ответ написан
ultimate_darkness
@ultimate_darkness
По поводу второго вопроса. В Hibernate, если все операции происходят в рамках одной сессии (Hibernate Session), то transaction.account и user.accout вернут одну и ту же сущность, поэтому можно безопасно менять ее поля в разных местах кода.
Ответ написан
taliban
@taliban
php программист
Может я что-то не понимаю, но судя по всему используется ActiveRecord и запись типа user.account.save() должна сохранить данные в бд, разве нет? Неважно откуда она вызывается, метод save должен сохранять текущие изменения обьекта в бд, иначе зачем он ввобще нужен?
Ответ написан
@Suor
Для одной операции не должно делаться манипуляций с аккаунтом в двух разных местах. Проблема в архитектуре кода, она дурная. Нужно просто посмотреть на всё вместе и срефакторить
Ответ написан
@dborovikov
Единственное правильно решение вашей проблемы — использование select for update. И да, что бы обезопасить себя от трудно уловимых ошибок стоит добавить поле с версией. Даже если не будет синхронизации, то по крайней мере транзакций откатится. Ну или использовать уровень изоляции SERIALIZATION, что тоже приведет к откату транзакции.
Ответ написан
@kmike
Еще подумайте, вам точно нужна сущность «Account»? По сути же, насколько я понял, это просто кэш, чтоб из транзакций баланс не пересчитывать каждый раз. Вот и относитесь к ней, как к кэшу — те же подходы к инвалидации и тд. Может, даже пересчитывать каждый раз суммы на счете окажется ok, вы точно в это упираетесь по скорости?

Ну и, конечно, точка обновления счета должна быть одна: или какая-то явная функция, или по созданию Transaction пересчитывать значение в Account, или (imho плохо) по изменению Account создавать Transaction.

select_for_update есть в транке джанги. Для атомарного увеличения поля можно использовать F-объект (но лучше все пересчитывать целиком, опять же imho).
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы