我有一个简单的任务模型:
class Task(models.Model):
name = models.CharField(max_length=255)
order = models.IntegerField(db_index=True)
一个简单的 task_create 视图:
def task_create(request):
name = request.POST.get('name')
order = request.POST.get('order')
Task.objects.filter(order__gte=order).update(order=F('order') + 1)
new_task = Task.objects.create(name=name, order=order)
return HttpResponse(new_task.id)
查看按+ 1新创建后的现有任务,然后创建一个新任务。
这个方法有很多用户,我想有一天会因订购而出现问题,因为更新和创建肯定应该一起执行。
所以,我只想保持谨慎,这足以避免任何数据损坏:
from django.db import transaction
def task_create(request):
name = request.POST.get('name')
order = request.POST.get('order')
with transaction.atomic():
Task.objects.select_for_update().filter(order__gte=order).update(order=F('order') + 1)
new_task = Task.objects.create(name=name, order=order)
return HttpResponse(new_task.id)
1)可能在现有select_for_update
的{{1}}之前的filter
之类的任务创建行中应该做更多的工作?
2)Task.objects
位于何处?内部交易区块还是外部?
Big thx
答案 0 :(得分:6)
1)在过滤现有
select_for_update
之前,可能还需要在Task.objects
之类的任务创建行中完成更多工作?
不 - 你现在看起来很好,应该按你想要的方式工作。
2)
return HttpResponse()
位于何处?内部交易区块还是外部?
是的,这很重要。无论事务是否成功,您都需要向客户端返回响应 - 因此它肯定需要在事务块之外。如果您在事务内部执行此操作,则如果事务失败,客户端将收到500 Server Error。
但是,如果事务失败,那么您将没有新的任务ID,并且无法在您的响应中返回该任务ID。因此,您可能需要根据事务是否成功返回不同的响应,例如:
from django.db import IntegrityError, transaction
try:
with transaction.atomic():
Task.objects.select_for_update().filter(order__gte=order).update(
order=F('order') + 1)
new_task = Task.objects.create(name=name, order=order)
except IntegrityError:
# Transaction failed - return a response notifying the client
return HttpResponse('Failed to create task, please try again!')
# If it succeeded, then return a normal response
return HttpResponse(new_task.id)
答案 1 :(得分:2)
您还可以尝试更改模型,以便在插入新行时不需要更新其他行。
例如,您可以尝试类似双链表的内容。
(我在这里使用了字段和变量的长显式名称。)
# models.py
class Task(models.Model):
name = models.CharField(max_length=255)
task_before_this_one = models.ForeignKey(
Task,
null=True,
blank=True,
related_name='task_before_this_one_set')
task_after_this_one = models.ForeignKey(
Task,
null=True,
blank=True,
related_name='tasks_after_this_one_set')
队列顶部的任务将是将字段task_before_this_one
设置为null的任务。所以要获得队列的第一个任务:
# these will throw exceptions if there are many instances
first_task = Task.objects.get(task_before_this_one=None)
last_task = Task.objects.get(task_after_this_one=None)
插入新实例时,您只需要知道应该放置哪个任务(或者,在哪个任务之前)。这段代码应该这样做:
def task_create(request):
new_task = Task.objects.create(
name=request.POST.get('name'))
task_before = get_object_or_404(
pk=request.POST.get('task_before_the_new_one'))
task_after = task_before.task_after_this_one
# modify the 2 other tasks
task_before.task_after_this_one = new_task
task_before.save()
if task_after is not None:
# 'task_after' will be None if 'task_before' is the last one in the queue
task_after.task_before_this_one = new_task
task_after.save()
# update newly create task
new_task.task_before_this_one = task_before
new_task.task_after_this_one = task_after # this could be None
new_task.save()
return HttpResponse(new_task.pk)
此方法仅在插入新行时更新其他2行。如果应用程序中存在高并发性,您可能仍希望将整个方法包装在事务中,但此事务最多只能锁定3行,而不是所有其他行。
如果您有很长的任务列表,这种方法可能对您有用。
编辑:如何获得有序的任务列表
这在单个查询中无法在数据库级别完成(据我所知),但您可以尝试此函数:
def get_ordered_task_list():
# get the first task
aux_task = Task.objects.get(task_before_this_one=None)
task_list = []
while aux_task is not None:
task_list.append(aux_task)
aux_task = aux_task.task_after_this_one
return task_list
只要你只有一些繁琐的任务,这个操作不应该花费那么多时间,以免影响响应时间。但是您必须在自己的环境,数据库和硬件中尝试这一点。