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()程序不会在每次请求时都重新计算当前时间,而是使用缓存的查询集。
相关推荐
卜及中3 小时前
【Redis/2】核心特性、应用场景与安装配置
数据库·redis·缓存
LucianaiB3 小时前
如何做好一份优秀的技术文档:专业指南与最佳实践
android·java·数据库
Eiceblue3 小时前
Python读取PDF:文本、图片与文档属性
数据库·python·pdf
在未来等你6 小时前
SQL进阶之旅 Day 21:临时表与内存表应用
sql·mysql·postgresql·database·temporary-table·memory-table·sql-optimization
zhangsan09336 小时前
web框架(Django 与 FastAPI)
django·fastapi
敖云岚6 小时前
【Redis】分布式锁的介绍与演进之路
数据库·redis·分布式
LUCIAZZZ7 小时前
HikariCP数据库连接池原理解析
java·jvm·数据库·spring·springboot·线程池·连接池
我在北京coding7 小时前
300道GaussDB(WMS)题目及答案。
数据库·gaussdb
小Tomkk7 小时前
阿里云 RDS mysql 5.7 怎么 添加白名单 并链接数据库
数据库·mysql·阿里云
明月醉窗台8 小时前
qt使用笔记二:main.cpp详解
数据库·笔记·qt