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()程序不会在每次请求时都重新计算当前时间,而是使用缓存的查询集。
相关推荐
极限实验室12 小时前
APM(一):Skywalking 与 Easyearch 集成
数据库·云原生
饕餮争锋12 小时前
SQL条件中WHERE 1=1 的功能
数据库·sql
玄斎13 小时前
MySQL 单表操作通关指南:建库 / 建表 / 插入 / 增删改查
运维·服务器·数据库·学习·程序人生·mysql·oracle
编织幻境的妖13 小时前
SQL查询连续登录用户方法详解
java·数据库·sql
编程小Y13 小时前
MySQL 与 MCP 集成全解析(核心原理 + 实战步骤 + 应用场景)
数据库·mysql·adb
零度@14 小时前
SQL 调优全解:从 20 秒到 200 ms 的 6 步实战笔记(附脚本)
数据库·笔记·sql
Miss_Chenzr14 小时前
Springboot优卖电商系统s7zmj(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
lvbinemail14 小时前
Grafana模板自动复制图表
数据库·mysql·zabbix·grafana·监控
Miss_Chenzr14 小时前
Springboot旅游景区管理系统9fu3n(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·旅游
小虾米vivian14 小时前
dmetl5 运行失败,提示违反协议?
数据库·达梦数据库