Django数据库查询错误排查:DAU计算结果异常求助
首先,咱们先聚焦你遇到的核心矛盾:同一时间范围内,直接统计distinct app_id得到100,但按日期分组的DAU统计却返回1200,这明显说明分组统计的逻辑出了问题。
问题根源:extra()的使用导致统计异常
你当前用extra({'date': 'date(datetime)'})提取日期,这种写法是Django早期的底层数据库操作方式,现在已经被官方标记为过时(Django 3.1+推荐用内置数据库函数替代)。更关键的是,这种写法结合Count('app_id', distinct=True)时,可能因为数据库层面的字段解析顺序或函数兼容性问题,导致distinct=True没有正确生效——看起来你的查询实际上统计的是该日期内所有GameEvent记录的总数(1200条),而非去重后的app_id数量。
正确的DAU统计写法(推荐)
改用Django内置的TruncDate函数提取日期,它能更可靠地和ORM的其他操作兼容:
from django.db.models.functions import TruncDate # 修正后的DAU统计查询 dau = GameEvent.objects.filter( game=game, datetime__gte=date_start, datetime__lte=date_end ).annotate( date=TruncDate('datetime') # 用TruncDate统一提取日期,兼容所有数据库 ).values('date').annotate( count=Count('app_id', distinct=True) ).order_by('date')
其他需要排查的潜在问题
时区不匹配问题
你的pre_save信号里用的是datetime.datetime.now(),这是不带时区的「朴素时间」。如果你的Django项目开启了USE_TZ = True,数据库存储的datetime会被转换为UTC时间,但date_start和date_end如果是本地时间的话,会导致时间范围查询偏差。建议统一使用Django的时区工具:from django.utils import timezone # 在pre_save信号里替换为带时区的时间 instance.datetime = timezone.now() # 生成时间范围时也用时区工具,比如单日范围: date_start = timezone.make_aware(datetime.datetime(year, month, day)) date_end = date_start + timezone.timedelta(days=1)pre_save信号的副作用
你的信号每次保存(包括更新)GameEvent实例时,都会把datetime改成当前时间。如果旧的事件记录被修改,它们的日期会被更新到当前,这会导致DAU统计混入错误数据。如果datetime字段应该记录事件发生的时间而非修改时间,建议直接设置auto_now_add=True,去掉pre_save信号:datetime = models.DateTimeField(blank=False, auto_now_add=True)
验证方法
你可以先运行下面的查询验证结果:
# 用TruncDate方式统计单日DAU test_dau = GameEvent.objects.filter( game=game, datetime__gte=date_start, datetime__lte=date_end ).annotate(date=TruncDate('datetime')).values('date').annotate(count=Count('app_id', distinct=True)) print(test_dau[0].count) # 应该和你的第二个查询结果一致,即100
内容的提问来源于stack exchange,提问作者Vevs

