Посессионные транзакции в Джанго - PullRequest
8 голосов
/ 23 июня 2009

Я создаю веб-приложение Django, которое позволяет пользователю создавать набор изменений для серии GET / POST, прежде чем фиксировать их в базе данных (или возвращать) с окончательным POST. Я должен держать обновления изолированными от любых одновременных пользователей базы данных, пока они не будут подтверждены (это интерфейсная конфигурация), исключая фиксацию после каждого POST.

Мое предпочтительное решение - использовать транзакцию за сеанс. Это сохраняет все проблемы с запоминанием того, что изменилось (и как это влияет на последующие запросы), вместе с реализацией commit / rollback в базе данных, которой он принадлежит. Взаимная блокировка и длительные блокировки не являются проблемой, поскольку из-за внешних ограничений в любой момент времени может быть только один пользователь, настраивающий систему, и они ведут себя хорошо.

Однако я не могу найти документацию по настройке ORM в Django для использования такого рода модели транзакций. Я собрал вместе минимальный патч (ew!), Чтобы решить проблему, но мне не нравится такое хрупкое решение. Кто-нибудь еще делал это раньше? Я где-то пропустил документацию?

(Моя версия Django - финальная версия 1.0.2, и я использую базу данных Oracle.)

Ответы [ 3 ]

8 голосов
/ 23 июня 2009

Несколько одновременных транзакций масштаба сеанса обычно приводят к взаимоблокировкам или хуже (хуже == livelock, длительные задержки, пока блокировки удерживаются другим сеансом.)

Этот дизайн не лучшая политика, поэтому Джанго не одобряет его.

Лучшее решение заключается в следующем.

  1. Создание класса Memento , в котором записываются изменения пользователя. Это может быть сохраненная копия ввода их формы. Вам может потребоваться записать дополнительную информацию, если изменения состояния являются сложными. В противном случае копии формы ввода может быть достаточно.

  2. Накопление последовательности Memento объектов в их сеансе. Обратите внимание, что каждый шаг в транзакции будет включать выборки из данных и проверку, чтобы увидеть, будет ли цепочка сувениров все еще «работать». Иногда они не работают, потому что кто-то еще что-то изменил в этой цепочке сувениров. Что теперь?

  3. Когда вы представляете «готов совершить?» На этой странице вы воспроизвели последовательность Mementos и уверены, что они будут работать. Когда вы отправляете «Commit», вы должны повторить Mementos в последний раз, надеясь, что они все еще будут работать. Если они это сделают, отлично. Если нет, кто-то что-то изменил, и вы вернулись к шагу 2: что теперь?

Это кажется сложным.

Да, это так. Однако он не удерживает никаких замков, позволяя взорвать скорость и мало возможностей для тупика. Транзакция ограничивается функцией представления «Фиксация», которая фактически применяет последовательность Mementos к базе данных, сохраняет результаты и выполняет окончательный коммит для завершения транзакции.

Альтернатива - удерживать замки, пока пользователь выходит за чашкой быстрого кофе на шаге n-1 из n - не работает.

Для получения дополнительной информации о Памятка см. this .

2 голосов
/ 14 июля 2009

Я придумал что-то похожее на шаблон Memento, но достаточно отличающееся, чтобы я думаю, что оно должно быть опубликовано. Когда пользователь начинает сеанс редактирования, я дублирую целевой объект на временный объект в базе данных. Все последующие операции редактирования влияют на дубликат. Вместо сохранения состояния объекта в памятке при каждом изменении я сохраняю операцию объектов. Когда я применяю операцию к объекту, она возвращает обратную операцию, которую я сохраняю.

Операции сохранения намного дешевле для меня, чем сувениры, поскольку операции могут быть описаны с помощью нескольких небольших элементов данных, тогда как редактируемый объект намного больше. Также я применяю операции, когда иду, и сохраняю отмены, так что временная переменная в БД всегда соответствует версии в браузере пользователя. Мне никогда не придется воспроизводить коллекцию изменений; временная операция всегда находится всего в одной операции от следующей версии.

Чтобы реализовать «отмену», я вытаскиваю последний объект отмены из стека (как бы - путем извлечения самой последней операции для временного объекта из БД), применяю его к временному и возвращаю преобразованное временное. Я мог бы также поместить получившуюся операцию в стек повторного выполнения, если бы хотел реализовать повтор.

Для реализации «сохранения изменений», т. Е. Фиксации, я деактивирую и отмечаю метку времени исходного объекта и активирую временный объект на его месте.

Для реализации "отмены", то есть отката, я ничего не делаю! Конечно, я мог бы удалить временный, потому что у пользователя нет возможности получить его после окончания сеанса редактирования, но мне нравится сохранять отмененные сеансы редактирования, чтобы я мог запускать статистику по ним, прежде чем очищать их с помощью задания cron. .

2 голосов
/ 29 июня 2009

На случай, если у кого-то возникнет точно такая же проблема, как и у меня (надеюсь, нет), вот мой обезьян-патч. Он хрупок и уродлив, и меняет частные методы, но, к счастью, он маленький. Пожалуйста, не используйте его, если вам действительно не нужно. Как уже упоминалось другими, любое приложение, использующее его, эффективно предотвращает одновременное обновление несколькими пользователями, что может привести к взаимной блокировке. (В моем приложении может быть много читателей, но несколько одновременных обновлений намеренно исключены.)

У меня есть объект "пользователь", который сохраняется в течение сеанса пользователя и содержит постоянный объект подключения. Когда я проверяю, что конкретное HTTP-взаимодействие является частью сеанса, я также сохраняю объект пользователя в django.db.connection, который является локальным для потока.

def monkeyPatchDjangoDBConnection():
    import django.db
    def validConnection():
        if django.db.connection.connection is None:
            django.db.connection.connection = django.db.connection.user.connection
        return True
    def close():
        django.db.connection.connection = None
    django.db.connection._valid_connection = validConnection
    django.db.connection.close = close
monkeyPatchDBConnection()

def setUserOnThisThread(user):
    import django.db
    django.db.connection.user = user

Этот последний вызывается автоматически при запуске любого метода, аннотированного @login_required, поэтому 99% моего кода изолированы от специфики этого хака.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...