2012-01-03 398 views
5

我正在對數據庫中的所有行運行批處理操作。這涉及到選擇每一個模型並對其做一些事情。把它分成塊並且用塊來完成它是有意義的。用Django QuerySet處理數據庫塊的最佳方法是什麼?

我目前使用Paginator,因爲它很方便。這意味着我需要對這些值進行排序,以便可以按順序對其進行分頁。這確實生成了具有orderlimit子句的SQL語句,並且對於每個塊,我認爲Postgres可能會對整個表進行排序(儘管我不能聲稱對內部有任何知識)。我所知道的是數據庫大約佔CPU的50%,我認爲這太高了只是爲了做select s。

以RDMBS/CPU友好的方式遍歷整個表的最佳方式是什麼?

假設批處理操作期間數據庫的內容不會更改。

回答

5

從您的描述中,您實際上並不關心您所處理的行的排序順序。如果你對你的表的主鍵(我期待!),分區的這種粗略的方法是更快

SELECT * FROM tbl WHERE id BETWEEN 0 AND 1000; 
SELECT * FROM tbl WHERE id BETWEEN 1001 AND 2000; 
... 

此執行任何偏移和(幾乎)相同的任何大小相同的表。 相應檢索主鍵和分區的min和max:

SELECT min(id), max(id) from tbl; -- then divide in suitable chunks 

與之相對:

SELECT * FROM tbl ORDER BY id LIMIT 1000; 
SELECT * FROM tbl ORDER BY id LIMIT 1000 OFFSET 1000; 
... 

這通常是較慢的,因爲所有的行已被排序和性能另外降解更高的偏移量和更大的表格。

+0

這假定記錄以相同的順序沒有'sort'條款返回。它是否正確?另外,如果我在我的'Meta'類中有一個默認的排序,我可以以某種方式刪除它的查詢? – Joe 2012-01-03 12:41:17

+0

@Joe:基本上你會得到相同的記錄,但是沒有排序。如果您的ID空間存在空白,則每次調用返回的記錄數量可能會少於預期。使用LIMIT/OFFSET時,您會得到固定數量的排序行(每個表格最後一次調用除外)。我不怎麼處理'Meta'類,但是你需要*爲你的行排列LIMIT/OFFSET。 – 2012-01-03 14:09:23

+0

埃爾文,我真的很抱歉,我沒有正確地讀你的答案。你確定這更快嗎?只要id已經被排序,或者每次執行整個表掃描,''between''子句肯定只能工作。 – Joe 2012-01-03 15:27:52

2

下面的代碼實現歐文的回答上述(使用BETWEEN),用於一個Django查詢集:

甲效用函數,將對於任意的Django查詢集爲此如下。它默認假設'id'是一個合適的字段,用於between子句。

def chunked_queryset(qs, batch_size, index='id'): 
    """ 
    Yields a queryset split into batches of maximum size 'batch_size'. 
    Any ordering on the queryset is discarded. 
    """ 
    qs = qs.order_by() # clear ordering 
    min_max = qs.aggregate(min=models.Min(index), max=models.Max(index)) 
    min_id, max_id = min_max['min'], min_max['max'] 
    for i in range(min_id, max_id + 1, batch_size): 
     filter_args = {'{0}__range'.format(index): (i, i + batch_size - 1)} 
     yield qs.filter(**filter_args) 

它會像這樣使用:

for chunk in chunked_queryset(SomeModel.objects.all(), 20): 
    # `chunk` is a queryset 
    for item in chunk: 
     # `item` is a SomeModel instance 
     pass 
相關問題