- PVSM.RU - https://www.pvsm.ru -
Всем привет!
Про Django модели уже много статей на хабре, но хочется поделится с общественностью, как эффективно использовать их и не наступать на грабли.
Допустим мы реализуем метод обновления баланса для пользователя. И этот метод выглядит так:
class Profile(models.Model):
….
def update_balance(self, balance):
self.balance += balance
self.save()
В этом случае, если нам придут два одновременных запроса на обновление баланса, то баланс обновит только второй запрос, потому что последний запрос вытеснил первый и взял старые данные.
На этом этапе на помощь нам приходит метод F [1] в связке с .update()
F() возвращает нам значение из базы в актуальном состоянии. и предыдущий участок можно записать так
class Profile(models.Model):
….
def update_balance(self, balance):
Profile.objects.
filter(pk=self.pk)
.update(balance=F('balance') + balance)
В этом случае мы всегда получаем актуальное значение поля и некоторые скажут, что этот способ решает нам проблему, но это не так. В этом случае, хоть и реализовано все правильно, как мы считаем, но проблему это не решает.
В этом случае приходит к нам на помощь транзакции на уровне БД.
Начнем с того, что в Django 1.4.x и 1.5.x можно включить Transaction Middleware. В Django 1.6+ ее заменили на константу ATOMIC_REQUESTS, которую можно включить к каждой БД использующейся в проекте.
Работают они следующим образом. Когда к нам пришел запрос и перед тем как передать этот запрос на обработку во view Django открывает транзакцию. Если запрос был отработан без исключений, то делается commit в БД или rollback, если выскочило исключение.
Разница между ATOMIC_REQUESTS и Middleware в том, что Middleware включается для всего проекта, а ATOMIC_REQUESTS можно использовать для одной или нескольких БД.
Минус использования этого подхода в том, что создается оверхед на базу данных.
В этом случае нам на помощь приходит ручное управление транзакциями.
Django предоставляет множество вариантов работы с помощью модуля django.db.transaction
Рассмотрим один из возможных способов ручного управления — это transaction.atomic
transaction.atomic является и методом и декоратором и используется только для view методов.
Обезопасить покупку товара можно, обернув view в декоратор. Например
...
from django.db import transaction
...
@transaction.atomic
def buy_something(request):
....
request.user.update_balance(money)
return render(request, template, data)
В этом случае мы включили атомарность транзакции для покупки товара. Всю ответственность за целостность данных переложили на БД и атомарность решает нашу проблему.
Еще в связке с атомарными транзакциями можно использовать select_for_update метод.
В этом случае изменяемая строка будет блокироваться на изменение до тех пор, пока не вызовется update.
Наш метод обновления баланса можно записать теперь так:
class Profile(models.Model):
….
def update_balance(self, balance):
Profile.objects.select_for_update().
filter(pk=self.pk)
.update(balance=F('balance') + balance)
Дополнительно: про уровни транзакций в MySQL рассказали «MySQL: уровни изоляции транзакций» [2].
Автор: Gen1us2k
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/85386
Ссылки в тексте:
[1] F: https://docs.djangoproject.com/en/1.7/ref/models/queries/#f-expressions
[2] «MySQL: уровни изоляции транзакций»: http://habrahabr.ru/post/135217/
[3] Источник: http://habrahabr.ru/post/252563/
Нажмите здесь для печати.