背景
在線上服務中使用時間進行數據庫操作時發現異常,而在本地環境無法成功復現此問題,導致難以進行故障排查。
核心問題
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
函數:
from django.db.models.functions import Now
而不是使用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
的情況。這樣可以確保每次請求都能反映真實的當前時間,避免由于查詢集緩存所導致的時間判斷錯誤。確保時間數據的一致性與準確性。
2. 數據庫兼容性:Now()
函數自動適應不同數據庫系統的時間函數,降低了數據庫之間兼容性問題的風險。
3. 性能:如果數據庫后端支持,使用Now()
可能會比從應用層傳遞時間戳到數據庫更優化,因為時間運算直接在數據庫層完成。
4. 時區一致性:Now()
函數遵守Django的時區設置,自動處理時區轉換,減少了手動處理時區問題的復雜度。
缺點:
- 依賴數據庫時鐘:使用
Now()
函數意味著依賴數據庫服務器的時鐘。如果數據庫服務器的時間配置不正確,可能會導致問題。 - 數據庫執行時間:由于
Now()
函數在數據庫執行查詢時生成時間,如果一個請求涉及多個查詢,而查詢之間有延遲,這可能會導致時間上的微小不一致。 - 測試復雜性:使用
Now()
可能會使單元測試更復雜,因為在測試環境中控制或模擬數據庫返回的Now()
值通常比使用固定的datetime
值更困難。
總結
- 在Django與數據庫交互時,如果需要獲取當前時間,應優先考慮使用
django.db.models.functions.Now()
,而不是datetime.datetime.now()
,特別是當涉及到使用類屬性queryset
的情況。這樣可以確保每次請求都能反映真實的當前時間,避免由于查詢集緩存所導致的時間判斷錯誤。確保時間數據的一致性與準確性。 - 若使用queryset應該避免存在動態計算的情況,比如上述例子的status字段計算,
queryset = Announcement.objects.all()
程序不會在每次請求時都重新計算當前時間,而是使用緩存的查詢集。