У меня есть действия, которые могут иметь несколько держателей 'action', где каждый 'action_holder' может иметь динамическую c роль. Например, «гимнаст-лидер», «студент» и др. c. Эти роли могут быть созданы клиентом и не являются фиксированными. Также «первичный» action_holder_role денормируется в модели, потому что в противном случае фильтрация QuerySet заняла бы много времени (более 2000 операций загружаются на одну страницу, если бы я использовал обычный кеш вместо денормализации базы данных ie, потребовалось бы 20 + секунды вместо 2/3 секунд). Для простоты в примере выбрано много полей. Обратите внимание, что это создает условие гонки, поэтому необходим LOCK TABLE.
class Activity(models.Model):
data = models.DateField()
action_holders = models.ManyToManyField(settings.AUTH_USER_MODEL, through='ActivityUserRole', related_name='realactivities')
cached_primary_action_holder = models.ForeignKey('ActivityUserRole', null=True, blank=True, on_delete=models.SET_NULL, related_name='activity_primary_action_holder_role')
order = models.IntegerField()
class ActivityUserRole(models.Model):
role = models.ForeignKey('Role', on_delete=models.PROTECT)
user = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
activity = models.ForeignKey(Activity, on_delete=models.CASCADE)
primary = models.BooleanField(default=False) # primary for realactivity
class Role(models.Model):
name = models.CharField(max_length=255)
Моя проблема в том, что когда я создаю новое действие, я даю new_action_holder в качестве аргумента для действия и вызываю set_new_action_holder создать соответствующие ActivityUserRoles после сохранения нового действия (поскольку ActivityUserRole нуждается в действии, поэтому оно должно быть сохранено первым).
Получите набор запросов заказа, чтобы заполнить фильтры заказов в держателе new_action. Если я сохраняю одно действие за раз, оно прекрасно работает. Но если я сохраню, скажем, 3 в то же время. Все они получают один и тот же порядок, потому что get_ordering_queryset фиксируется в базе данных до вызова сохранения первого действия.
Я исправил это, добавив:
@contextmanager
def lock_table(read=[], write=[]):
with transaction.atomic():
cursor = get_connection().cursor()
lock = 'LOCK TABLES '
lock = lock + ', '.join([f'{wri._meta.db_table} WRITE' for wri in write] +
[f'{rea._meta.db_table} READ' for rea in read])
# print(lock)
cursor.execute(lock)
try:
yield
finally:
cursor.execute('UNLOCK TABLES')
cursor.close()
и создав WRITE блокировка Activity, ActivityUserRole и блокировка READ для любой другой связанной таблицы (их много!) с момента, когда я вызываю функцию get_ordering_queryset (), до функции set_new_action_holder. Это прекрасно работает. Порядок теперь правильный. Но а) это похоже на взлом, и б) мне нужно ПРОЧИТАТЬ блокировку 9 таблиц и ЗАПИСЬ блокировать 4 таблицы, частично из-за сигналов post_save на ActivityUserRole и многих других таблицах.
И ... Я не могу больше использовать транзакцию.atomi c. Если я использую его, я получаю «ошибка точки сохранения не существует». Мне действительно нужен транзакции.atomi c, потому что активность должна сохраняться вместе с «ExtraInfoActivity», и в случае сбоя одного из них я хочу, чтобы транзакция откатилась и не было активности без данных ExtraInfoActivity.
Есть ли лучший способ, чем LOCK TABLE?
Есть ли способ использовать транзакцию.atomi c () в сочетании с LOCK TABLE?