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()程序不会在每次请求时都重新计算当前时间,而是使用缓存的查询集。
相关推荐
清水白石0084 分钟前
从一个“支付状态不一致“的bug,看大型分布式系统的“隐藏杀机“
java·数据库·bug
Python私教5 小时前
model中能定义字段声明不存储到数据库吗
数据库·oracle
mqiqe7 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
工业甲酰苯胺7 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
BestandW1shEs7 小时前
谈谈Mysql的常见基础问题
数据库·mysql
重生之Java开发工程师7 小时前
MySQL中的CAST类型转换函数
数据库·sql·mysql
教练、我想打篮球7 小时前
66 mysql 的 表自增长锁
数据库·mysql
Ljw...7 小时前
表的操作(MySQL)
数据库·mysql·表的操作
哥谭居民00017 小时前
MySQL的权限管理机制--授权表
数据库