广告

Django关注取关优化全解:面向社交应用的ManyToMany实战指南

1. 关系模型设计与关注取关的核心要点

在面向社交应用的场景中,关注取关的实现往往需要用到自引用的多对多关系,这不仅决定了数据结构,还直接影响查询的复杂度与可扩展性。通过使用 through 表来承载额外字段(如创建时间、来源、权重等),可以在不牺牲灵活性的前提下实现高效的聚合与筛选。

通过自定义中间表(through)来管理关注关系,可以把“关注者”与“被关注者”解耦成清晰的外键,避免歧义;同时,设置适当的外键约束和唯一性索引,确保数据的一致性与快速定位

在设计时要考虑对称性问题:默认的对称关系会导致重复的查询和错误的统计,需要显式设置 symmetrical=False,使关系方向更明确,便于实现取关、推荐、以及告警等功能。

示例代码片段展示了如何在Django中用through表构建自引用的多对多关系,并保留创建时间等信息。

1.1 关系建模要点

核心模型需要清晰描述关注关系的方向,通常使用一个独立的中间表来承载两端的外键,以及可选的元数据字段。

通过设置related_name,便于反向查询,例如查询某用户的粉丝、某用户的关注列表等。

# models.py
from django.db import models
from django.contrib.auth.models import Userclass Follow(models.Model):follower = models.ForeignKey(User, related_name='following_rel', on_delete=models.CASCADE)following = models.ForeignKey(User, related_name='follower_rel', on_delete=models.CASCADE)created_at = models.DateTimeField(auto_now_add=True)class Meta:unique_together = ('follower', 'following')indexes = [models.Index(fields=['follower']),models.Index(fields=['following']),]# 通过 through 约束建立自引用的 ManyToMany
User.add_to_class('following',models.ManyToManyField('self',through=Follow,symmetrical=False,related_name='followers')
)

2. Django层面的优化要点

Django层面的优化核心在于减少数据库查询次数、降低查询延迟,同时通过缓存降低重复计算的成本;合理选择查询工具,可以显著提升高并发时的响应速度。

在多对多关系查询中,推荐使用 prefetch_related,以避免N+1问题;对单表外键关系,可以使用 select_related 来提升连表查询的效率。

Django关注取关优化全解:面向社交应用的ManyToMany实战指南

2.1 查询优化:prefetch_related、select_related、only、defer

prefetch_related 适用于 ManyToMany 以及反向关系,它会在单独的查询中加载所有相关对象,再在Python层做内存拼接,显著减少数据库往返。

select_related 只对一对多或外键有效,它会通过 JOIN 将相关对象的数据一次性获取,适用于深度联结查询的场景。

使用 only/defer 控制字段加载,可以降低每次查询的数据传输量,尤其在关注数、粉丝数等聚合字段不需要时。

# 例子:一次性获取用户及其关注列表,避免N+1
from django.db.models import Prefetchusers = User.objects.filter(id__in=[1,2,3]).prefetch_related(Prefetch('following', queryset=User.objects.only('id', 'username'))
)

2.2 通过自定义查询集优化关注查询

自定义查询集可以把一组常用的筛选封装成方法,方便在业务中复用并保持性能一致性。

用聚合与统计字段缓存关注数,减少重复的 COUNT 查询,有助于提升高并发场景下的吞吐量。

# 自定义查询集示例
class UserQuerySet(models.QuerySet):def with_followers_count(self):return self.annotate(followers_count=models.Count('followers', distinct=True))# 使用
users = User.objects.all().with_followers_count()

3. 数据库与索引设计

通过在 through 表上建立合适的联合索引,可以显著提升按关注人或被关注人查询的效率,并降低聚合操作的成本。

合理的字段约束与唯一性约束,确保数据一致性,同时便于数据库优化器做出更好的执行计划。

3.1 Through表的索引与约束

在 Follow 中添加联合唯一索引,避免重复关注,同时对 follower_id 与 following_id 建立单列索引,提升按任意一端筛选的速度。

CREATE TABLE app_follow (id BIGINT PRIMARY KEY,follower_id BIGINT NOT NULL,following_id BIGINT NOT NULL,created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,CONSTRAINT app_follow_unique UNIQUE (follower_id, following_id),CONSTRAINT app_follow_follower_fk FOREIGN KEY (follower_id) REFERENCES auth_user(id) ON DELETE CASCADE,CONSTRAINT app_follow_following_fk FOREIGN KEY (following_id) REFERENCES auth_user(id) ON DELETE CASCADE
);CREATE INDEX idx_follow_follower ON app_follow (follower_id);
CREATE INDEX idx_follow_following ON app_follow (following_id);

4. 实战代码片段与技巧

核心接口的设计要点在于幂等性、可观测性与高性能,包括关注、取关、以及快速判断某用户是否关注另一用户的操作。

以下实现演示了关注与取消关注的核心逻辑、以及防重复的检查,可直接用于业务层集成。

4.1 关注与取关的核心接口设计

关注一个用户时,尽量避免重复创建,若已经关注则直接返回成功或进行幂等处理;取关时需确保两端的关系被正确删除。

# services.py
from django.db import IntegrityError, transaction
from django.core.cache import cachedef follow_user(follower, target):if follower == target:return Falsetry:with transaction.atomic():follower.following.add(target)  # 通过中间表实现cache.delete(f'followers_count_{target.id}')return Trueexcept IntegrityError:# 已关注或其他问题,保持幂等return Falsedef unfollow_user(follower, target):if follower == target:return Falsewith transaction.atomic():follower.following.remove(target)cache.delete(f'followers_count_{target.id}')return Truedef is_following(follower, target):# 直接在 ORM 层判断,避免无意义的查询return follower.following.filter(id=target.id).exists()

5. 缓存与异步更新策略

在高并发场景下,单纯依赖数据库计数容易成为瓶颈,因此需要将关注数等热数据放入缓存中,并通过事件驱动进行更新。

信号与异步任务组合,确保写入与统计的最终一致性,可以在发布关注/取关事件时触发缓存刷新或异步重计算。

5.1 信号驱动的缓存刷新

利用 Django 的 post_save/post_delete 信号,来刷新粉丝数缓存,避免读取到未提交的脏数据。

# signals.py
from django.db.models.signals import m2m_changed
from django.dispatch import receiver
from django.core.cache import cache@receiver(m2m_changed, sender=User.following.through)
def update_followers_cache(sender, instance, action, reverse, pk_set, **kwargs):if action in ('post_add', 'post_remove', 'post_clear'):cache.delete(f'followers_count_{instance.id}')

5.2 使用任务队列异步处理重计算

当需要对大量用户进行重计算时,将任务交给 Celery 等任务队列执行,可以显著降低请求响应时间并提升系统吞吐率。

# tasks.py (Celery)
from celery import shared_task
from django.contrib.auth import get_user_model
from django.db.models import Count
from django.core.cache import cacheUser = get_user_model()@shared_task
def refresh_followers_count(user_id):user = User.objects.get(id=user_id)count = user.followers.count()cache.set(f'followers_count_{user_id}', count, timeout=3600)

通过在关注/取关动作完成后触发异步任务,逐步实现最终一致性,同时保持低延迟的用户体验。

广告

后端开发标签