django与数据库交互关于当前时间的坑

背景

在线上服务中使用时间进行数据库操作时发现异常,而在本地环境无法成功复现此问题,导致难以进行故障排查。

核心问题

view.py

复制代码
class XxxViewSet(viewsets.ModelViewSet):

    queryset = Xxx.objects.with_status().order_by("status", "-start_time")

managers.py

复制代码
def with_status(self):
    """添加status排序字段"""
    cur_time = datetime.now(pytz.utc)
    queryset = self.annotate(
        status=Case(
            When(end_time__lt=cur_time, then=Value(2)),
            When(start_time__gt=cur_time, then=Value(1)),
            default=Value(0),
            output_field=IntegerField(),
        )
    )
    return queryset

问题的关键点

由于viewsets.ModelViewSet中使用了queryset属性,程序不会在每次请求时都重新计算当前时间,而是使用缓存的查询集。这意味着时间过滤条件并不会随着时间实时更新,而是固定在视图集类被加载时的时间。

分析本地无法复现原因

本地开发经常使用热部署,即服务频繁重启,这导致定义的cur_time变量不断被重置。而线上环境服务不会如此频繁重启,因而很难注意到这个问题。建议在本地创建一条时间精确到秒的记录,以此来模拟线上环境并复现问题。

问题的根源

在Django 中与数据库交互时,应使用Django 数据库函数中的当前时间而非Python标准库中的时间:

使用Django的Now函数:

python 复制代码
from django.db.models.functions import Now

而不是使用python 的时间

python 复制代码
from datetime import datetime

否则会把时间设置为定值

从sql的角度理解就是

最终方案

修改使用数据库时间
managers.py

复制代码
from django.db.models.functions import Now
def with_status(self):
    """添加status排序字段"""
    cur_time = Now()
    queryset = self.annotate(
        status=Case(
            When(end_time__lt=cur_time, then=Value(2)),
            When(start_time__gt=cur_time, then=Value(1)),
            default=Value(0),
            output_field=IntegerField(),
        )
    )
    return queryset

结论与建议

使用Django django.db.models.functions.Now()函数替代Python datetime.datetime.now()在Django应用程序的时间处理中具有几个优点与潜在的缺点:

优点:

1.在Django 与数据库 交互时,如果需要获取当前时间,应优先考虑使用django.db.models.functions.Now(),而不是datetime.datetime.now(),特别是当涉及到使用类属性queryset的情况。这样可以确保每次请求都能反映真实的当前时间,避免由于查询集缓存所导致的时间判断错误。确保时间数据的一致性与准确性。

  1. 数据库兼容性 :Now()函数自动适应不同数据库系统的时间函数,降低了数据库之间兼容性问题的风险。

  2. 性能 :如果数据库后端支持,使用Now()可能会比从应用层传递时间戳到数据库更优化,因为时间运算直接在数据库层完成。

  3. 时区一致性 :Now()函数遵守Django的时区设置,自动处理时区转换,减少了手动处理时区问题的复杂度。

缺点:

  1. 依赖数据库时钟 :使用Now()函数意味着依赖数据库服务器的时钟。如果数据库服务器的时间配置不正确,可能会导致问题。
  2. 数据库执行时间 :由于Now()函数在数据库执行查询时生成时间,如果一个请求涉及多个查询,而查询之间有延迟,这可能会导致时间上的微小不一致。
  3. 测试复杂性 :使用Now()可能会使单元测试更复杂,因为在测试环境中控制或模拟数据库返回的Now()值通常比使用固定的datetime值更困难。

总结

  1. 在Django 与数据库 交互时,如果需要获取当前时间,应优先考虑使用django.db.models.functions.Now(),而不是datetime.datetime.now(),特别是当涉及到使用类属性queryset的情况。这样可以确保每次请求都能反映真实的当前时间,避免由于查询集缓存所导致的时间判断错误。确保时间数据的一致性与准确性。
  2. 若使用queryset 应该避免存在动态计算的情况,比如上述例子的status 字段计算,queryset = Announcement.objects.all()程序不会在每次请求时都重新计算当前时间,而是使用缓存的查询集。
相关推荐
liangshanbo12154 小时前
主系统登录后,子系统怎么实现自动登录?
java·网络·数据库
许彰午4 小时前
05-静默安装常见报错与解决
数据库
害人终害己4 小时前
redis修改密码的地方在哪里
数据库·redis·缓存
rannn_1114 小时前
【Java面试题】高频面试题1|Java 后端、MySQL、Redis 与 RAG 面试整理
java·jvm·数据库·后端·面试
Q26433650234 小时前
【有源码】基于Spring Boot和Vue的在线考试与交流系统-教育信息化背景下在线考试与交流一体化平台
java·vue.js·spring boot·mysql·spring·毕业设计·课程设计
杨云龙UP4 小时前
Oracle 19c RAC到RAC Active Data Guard标准搭建与巡检指南
运维·服务器·数据库·oracle·adg·data guard·rac到rac
许彰午4 小时前
06-Oracle卸载与重装完整流程
数据库·oracle
ss2735 小时前
AI全栈实战 | 3.6-01 数据库与 ORM:学过 MyBatis 再看 SQLAlchemy,才知道“半自动“和“全自动“差在哪
数据库·mybatis
都适、隶仁ミ5 小时前
【等保加固】MySQL5.7.38
运维·服务器·mysql·安全·等保
我的xiaodoujiao5 小时前
Django 基础知识详细图文教程 13-Django 模型定义与使用 3
数据库·后端·python·测试工具·oracle·django