计算Django中具有特定值的相关对象的数量
以下是我的问题的简化模型:计算Django中具有特定值的相关对象的数量,django,database,postgresql,indexing,denormalization,Django,Database,Postgresql,Indexing,Denormalization,以下是我的问题的简化模型: class User(models.Model): username = models.CharField(max_length=30) total_readers = models.IntegerField(default=0) class Book(models.Model): author = models.ForeignKey(User) title = models.CharField(max_length=100) cl
class User(models.Model):
username = models.CharField(max_length=30)
total_readers = models.IntegerField(default=0)
class Book(models.Model):
author = models.ForeignKey(User)
title = models.CharField(max_length=100)
class Reader(models.Model):
user = models.ForeignKey(User)
book = models.ForeignKey(Book)
因此,我们有用户
,书籍
和读者
(用户
,他们读过书籍
)。因此,Reader
基本上是Book
和User
之间的多对多关系
现在让我们假设,当前用户阅读一本书。现在,我想更新本书作者所有书籍的读者总数:
# get the book (as an example pk=1)
book = Book.objects.get(pk=1)
# save Reader object for this user and this book
Reader(user=request.user, book=book).save()
# count and save the total number of readers for this author in all his books
book.author.total_readers = Reader.objects.filter(book__author=book.author).count()
book.author.save()
通过这样做,Django为PostgreSQL创建了一个LEFT-OUTER-JOIN
查询,我们得到了预期的结果。然而,数据库表非常庞大,这已经成为一个瓶颈
在本例中,我们可以简单地将每个视图上的总读卡器数增加一个,而不是实际计算数据库行数。然而,这只是一个简化的模型结构,我们在现实中无法做到这一点
我能做的是在Reader模型中创建另一个名为book\u author\u id
的字段。因此,我可以对数据进行非规范化,并且可以对读卡器对象进行计数,而无需PostgreSQL与用户表进行左外部联接。
最后,我的问题是:是否可以创建某种数据库索引,以便PostgreSQL自动处理这种非规范化?或者我真的必须创建这个额外的模型字段并在其中冗余存储作者的PK吗
编辑-指出基本问题:我得到了几个很好的答案,适用于很多场景。然而,它们并不能解决这个实际问题。我唯一想知道的是,是否有可能让PostgreSQL自动处理这样的非规范化—例如,通过创建某种数据库索引。通过良好的设计和一点缓存来解决此类瓶颈总是比按照您的建议复制数据要好得多。total_readers字段是您应该生成的数据,而不是记录的数据
class User(models.Model):
username = models.CharField(max_length=30)
@property
def total_readers(self):
cached_value = caching_client.get("readers_"+self.username, None)
if cached_value is None:
cached_value = self.readers()
caching_client.set("readers_"+self.username,
cached_value)
return cached_value
def readers(self):
return Reader.objects.filter(book__author__user=self).count()
有些库通过decorators进行缓存,但我觉得这是一种模式,您可以从中受益。您还可以将TTL附加到缓存,以确保值的错误时间不会超过TTL。还可以在创建读卡器对象时重新生成缓存
声明m2m并通过关系定义可能会让您获得一些好处,但我没有这方面的经验。通过良好的设计和一点缓存来解决此类瓶颈总是比按照您的建议复制数据要好得多。total_readers字段是您应该生成的数据,而不是记录的数据
class User(models.Model):
username = models.CharField(max_length=30)
@property
def total_readers(self):
cached_value = caching_client.get("readers_"+self.username, None)
if cached_value is None:
cached_value = self.readers()
caching_client.set("readers_"+self.username,
cached_value)
return cached_value
def readers(self):
return Reader.objects.filter(book__author__user=self).count()
有些库通过decorators进行缓存,但我觉得这是一种模式,您可以从中受益。您还可以将TTL附加到缓存,以确保值的错误时间不会超过TTL。还可以在创建读卡器对象时重新生成缓存
实际上,通过声明m2m和定义关系,您可能会获得一些经验,但我没有这方面的经验。有时,此查询可以更好地服务于:
book.author.total_readers = Reader.objects.filter(book__in=Book.objects.filter(author=book.author)).count()
这将使用子查询生成查询,有时使用联接生成的查询将具有更好的性能。您甚至可以更进一步,分别创建两个查询:
book.author.total_readers = Reader.objects.filter(book_id__in=Book.objects.filter(author=book.author).values_list('id', flat=True)).count()
这将生成两个查询,一个将检索该作者的所有图书ID列表,另一个将检索该列表中ID为的图书的阅读次数。有时,此查询可以更好地服务于:
book.author.total_readers = Reader.objects.filter(book__in=Book.objects.filter(author=book.author)).count()
这将使用子查询生成查询,有时使用联接生成的查询将具有更好的性能。您甚至可以更进一步,分别创建两个查询:
book.author.total_readers = Reader.objects.filter(book_id__in=Book.objects.filter(author=book.author).values_list('id', flat=True)).count()
这将生成两个查询,一个将检索该作者的所有图书ID列表,另一个将检索该列表中ID为的图书的阅读次数。好的解决方案可能是创建一些批处理任务,例如每小时运行一次,并统计所有阅读次数,但那样的话,你就不会有实时刷新的阅读次数了
您还可以创建芹菜任务,该任务将在创建read后立即运行,以为author生成新值。这样,您就不会有很长的响应时间,从创建读取到计算读取的延迟也不会太长。好的解决方案可能是创建一些批处理任务,例如每小时运行一次并计算所有读取,但这样您就不会实时刷新读取计数
您还可以创建芹菜任务,该任务将在创建read后立即运行,以为author生成新值。这样,您的响应时间不会太长,从创建读取到计算读取的延迟也不会太长。好主意,但对我们不起作用。有数百万用户/作者。缓存数以百万计的条目可能不是一个好主意…数以百万计的条目只是MB。您还可以仅缓存最常用的数据。缓存并不是所有问题的100%解决方案,但它可能会缓解您的问题,使其消失(目前)。我使用缓存技巧使新闻网站在50毫秒内呈现几乎所有页面。我将html代码片段存储在一个名称空间中,使每个页面主要是连接缓存中的字符串。如果字符串不可用,它将由web dyno重新生成。几乎所有的时间,幕后工作人员都在“取暖”。结果是,对数据库最频繁的查询是更新页面命中率。这是个好主意,但对我们不起作用。有数百万用户/作者。缓存数以百万计的条目可能不是一个好主意…数以百万计的条目只是MB。您还可以仅缓存最常用的数据。缓存并不是所有问题的100%解决方案,但它可能会缓解您的问题,使其消失(目前)。我使用缓存技巧使新闻网站在50毫秒内呈现几乎所有页面。我将html代码片段存储在