2016-03-23 109 views
4

我有非常大的數據集並且不斷增長,我需要創建多個過濾器,但它很快就會失去控制,希望有人能夠幫助我將某些查詢合併到一個調用中。以下是我的觀點的開始。減少Django數據庫查詢

呼叫#1 - 爲環,以顯示所有結果

的表traffic = Traffic.objects.all()

呼叫#2 - 組合的聚合和查詢

totals = Traffic.objects.aggregate(Sum('sessions'), Sum('new_users'), Sum('reminder'), Sum('campaigns'), Sum('new_sales'), Sum('sales_renewals')) 
    total_sessions = totals.get('sessions__sum') 
    total_new_users = totals.get('new_users__sum') 
    total_reminder = totals.get('reminder__sum') 
    total_campaigns = totals.get('campaigns__sum') 
    total_new_sales = totals.get('new_sales__sum') 
    total_sales_renewals = totals.get('sales_renewals__sum') 

呼叫#3,# 4,#5,#6等... - 按月份和日期過濾數據庫

total_sessions_2014_m = Traffic.objects.filter(created__year='2014', created__week_day=2).aggregate(Sum('sessions')) 

total_sessions_2014_m = Traffic.objects.filter(created__year='2014', created__week_day=3).aggregate(Sum('sessions')) 

total_sessions_2014_m = Traffic.objects.filter(created__year='2014', created__week_day=4).aggregate(Sum('sessions')) 

total_sessions_2014_m = Traffic.objects.filter(created__year='2014', created__week_day=5).aggregate(Sum('sessions')) 

total_sessions_2014_m = Traffic.objects.filter(created__year='2014', created__week_day=6).aggregate(Sum('sessions')) 

問題是,我需要創建幾十個更多的過濾器,因爲我有3年的數據每列有多個數據點,我們需要合計總和。

問題:

  1. 我可以結合呼叫#1至#調用2
  2. 我可以使用呼叫#2查詢呼叫#3的款項,所以我不必都稱呼數據庫中的對象來過濾它,然後再做幾次?

正如你所看到的,這將很快失去控制。任何幫助將非常感激。謝謝。

更新添加 流量模型

class Timestamp(models.Model): 
    created = models.DateField() 

    class Meta: 
     abstract = True 


class Traffic(Timestamp): 
    sessions = models.IntegerField(blank=True, null=True) 
    new_users = models.IntegerField(blank=True, null=True) 
    reminder = models.IntegerField(blank=True, null=True) 
    campaigns = models.IntegerField(blank=True, null=True) 
    new_sales = models.IntegerField(blank=True, null=True) 
    sales_renewals = models.IntegerField(blank=True, null=True) 

    # Meta and String 
    class Meta: 
     verbose_name = 'Traffic' 
     verbose_name_plural = 'Traffic Data' 

    def __str__(self): 
     return "%s" % self.created 
+0

是否所有這些數據一次顯示在模板上? –

+0

你可以通過添加「交通」模型來更新你的問題嗎? –

+0

@ ParagTyagi-morpheus-是的,這些都是相同的觀點。它是我內部營銷部門的工具,將從我們的內部服務器提供。這只是冰山一角,我有大量的數據從Excel移走,因此不同意見不是一種選擇。剛剛更新了我的「交通」模型。 –

回答

5

有幾十種方法來優化與Django的ORM數據庫查詢。像往常一樣,Django documentation是偉大的,並有一個很好的名單。下面是查詢優化的一些快速提示:

1)iterator()

如果您正在訪問的queryset只有一次。因此,例如,你可以以此爲,

traffic = Traffic.objects.all() 

for t in traffic.iterator(): 
    ... 
    ... 

2)db_index=True

在定義你的models的領域使用。作爲Django documentation說,

這是一個首要任務,你已經從 確定剖析應該添加哪些指標之後。使用Field.db_index或 Meta.index_together從Django中添加這些。考慮將索引 添加到您經常使用filter(),exclude(), order_by()等查詢的字段,因爲索引可能有助於加快查找速度。

因此,你可以修改你的模型,

class Traffic(Timestamp): 
    sessions = models.IntegerField(blank=True, null=True, db_index=True) 
    new_users = models.IntegerField(blank=True, null=True, db_index=True) 
    reminder = models.IntegerField(blank=True, null=True, db_index=True) 
    campaigns = models.IntegerField(blank=True, null=True, db_index=True) 
    new_sales = models.IntegerField(blank=True, null=True, db_index=True) 

3)prefetch_related()select_related()

如果您有models內部的關係,用prefetch_relatedselect_related將是一個選擇。根據Django documentation,

select_related通過創建SQL join幷包括SELECT語句中相關對象的字段來工作。因此,select_related在相同的數據庫查詢中獲取相關對象。但是,爲避免加入跨越「多」關係而導致的更大結果集,select_related僅限於單值關係 - 外鍵和一對一。

prefetch_related另一方面,爲每個 關係做單獨的查找,並在Python中進行「連接」。這允許它預取 多對多和多對一對象,除了select_related支持的外鍵和一對一關係之外,這些對象不能使用 select_related來完成。

select_related做了一個joinprefetch_related做兩個單獨的查詢。使用這些可以使查詢速度提高30%。


4)Django Pagination

如果您template設計,讓你在顯示多個頁面的結果您可以使用Pagination


5)Querysets are Lazy

您還需要了解的是,Django的查詢集都懶的,這意味着它不會查詢數據庫,直到使用它的/評估。 Django中的查詢集表示數據庫中的多個行,可以通過查詢進行過濾。例如,

traffic = Traffic.objects.all() 

上述代碼不運行任何數據庫查詢。您可以採取traffic查詢集並應用其他過濾器,或將其傳遞給函數,並且不會向數據庫發送任何內容。這很好,因爲查詢數據庫是顯着減慢Web應用程序速度的一個因素。爲了從數據庫讀取的數據,你需要遍歷查詢集:

for t in traffic.iterator(): 
    print(t.sessions) 

6)django-debug-toolbar

的Django調試工具欄是一組可配置顯示有關的各種調試信息板當前的請求/響應以及單擊時顯示有關面板內容的更多詳細信息。這包括:

  • 請求定時器
  • SQL查詢,包括執行時間和鏈接來解釋每個查詢

修改代碼:(記住查詢集是懶惰

traffic = Traffic.objects.all() 
totals = traffic.aggregate(Sum('sessions'), Sum('new_users'), Sum('reminder'), Sum('campaigns'), Sum('new_sales'), Sum('sales_renewals')) 
total_sessions = totals.get('sessions__sum') 
total_new_users = totals.get('new_users__sum') 
total_reminder = totals.get('reminder__sum') 
total_campaigns = totals.get('campaigns__sum') 
total_new_sales = totals.get('new_sales__sum') 
total_sales_renewals = totals.get('sales_renewals__sum') 

t_2014 = traffic.filter(created__year='2014') 
t_sessions_2014_wd2 = t_2014.filter(created__week_day=2).aggregate(Sum('sessions')) 
... 
... 

對於呼叫#1模板(for循環來顯示所有結果的表):

{% for t in traffic.iterator %} 
    {{ t.sessions }} 
    ... 
    ... 
{% endfor %} 
+0

哇,謝謝你很多很棒的信息!我剛到辦公室,需要一些人來消費,但會報告事情的進展情況以及最終的解決方案。 –

+0

我已經在使用django-debug-toolbar,但是使用iterator(),我能夠減少一些查詢。我當然需要繼續調整以儘量減少我的疑問,但這使我朝着正確的方向前進。感謝你的詳細回覆......這讓我去研究其他我不知道的領域。 –

1

至於問題1,它不應該是重用從第一次調用查詢集的一個問題。

traffic = Traffic.objects.all() 
totals = traffic.aggregate(Sum('sessions'), Sum('new_users'), Sum('reminder'), Sum('campaigns'), Sum('new_sales'), Sum('sales_renewals')) 

這應該可以讓您額外調用數據庫。

關於問題2,您可以再次在第一次調用中重用查詢集,並篩選年份,從而爲您提供新的查詢集,例如

traffic_2014 = traffic.filter(created__year='2014') 

然後,您可以繼續過濾天,這個新的查詢集聚合,像你以前那樣,或每天創造新的查詢集,假設你每天都聚集多個屬性,從而節省您的另一十幾數據庫調用。

我希望這可以幫助你。

1

沒有直接解決問題,但我認爲你應該考慮一種不同的方法。根據我的理解

  • 視圖可經常提出要求。
  • 數據應該很少變化。
  • 有必要進行復雜的數據操作(求和按年,月,日等領域)

沒有必要進行,每當有人請求查看相同的查詢。

在一個步驟中加載所有數據並在視圖內執行操作。您可以使用像Pandas這樣的庫並創建複雜的數據集。該視圖現在將與CPU綁定,因此請使用像Redis這樣的緩存系統來避免重新計算。數據更改時無效。

另一種方法:通過使用類似Celery的任務隊列定期執行計算並填充Redis。