2012-01-03 9 views
5

Sto eseguendo un'operazione batch su tutte le righe di un database. Ciò comporta la selezione di ogni singolo modello e l'esecuzione di qualcosa. Ha senso dividerlo in pezzi e farlo a pezzetti.Il modo migliore per elaborare il database in blocchi con Django QuerySet?

Attualmente sto usando Paginator, perché è conveniente. Ciò significa che ho bisogno di un ordinamento sui valori in modo che possano essere sfogliati in ordine. Questo genera istruzioni SQL che hanno clausole order e limit e penso che Postgres stia ordinando l'intera tabella (anche se non posso affermare di avere alcuna conoscenza sugli interni). Tutto quello che so è che il database è di circa il 50% della CPU e penso che sia troppo alto solo per fare select s.

Qual è il modo migliore per scorrere l'intera tabella in modo RDMBS/CPU-friendly?

Presupponendo che il contenuto del database non cambi durante l'operazione batch.

risposta

5

Dalla tua descrizione in realtà non ti interessa l'ordine delle righe elaborate. Se si dispone di chiavi primarie nelle tabelle (che mi aspetto!), Questo metodo grezzo di partizionamento sarebbe molto più veloce:

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

Esegue lo stesso per qualsiasi offset e (quasi) lo stesso per qualsiasi dimensione di tavolo Recuperare min e max della vostra chiave primaria e la partizione di conseguenza:

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

Al contrario:

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

Questo è generalmente più lento, perché tutte le righe devono essere ordinati e le prestazioni degrada in aggiunta con offset più alti e tabelle più grandi.

+0

Questo presuppone che i record vengono restituiti nello stesso ordine senza un 'sort' clausola. È corretto? Inoltre, se ho un ordinamento predefinito nella mia classe 'Meta' posso in qualche modo rimuoverlo per la query? – Joe

+0

@Joe: In pratica si ottengono gli stessi record, ma non ordinati. Se ci sono lacune nello spazio ID, il numero di record restituiti può essere inferiore al previsto per ogni chiamata. Mentre con LIMIT/OFFSET si ottiene un numero fisso di righe ordinate (tranne l'ultima chiamata per tabella). Non so come gestire la classe 'Meta', ma * devi * ordinare le tue righe per LIMIT/OFFSET. –

+0

Erwin, mi dispiace davvero di non aver letto correttamente la risposta. Sei sicuro che sia più veloce? la clausola 'between' può sicuramente funzionare solo se gli ID sono già ordinati, o esegue una scansione di una tabella intera ogni volta? – Joe

2

Il codice seguente implementa risposta Erwin sopra (usando BETWEEN) per un Django QuerySet:

Una funzione di utilità che farà questo per un arbitrario Django QuerySet è il seguente. L'impostazione predefinita è che l''id' sia un campo adatto da utilizzare per la clausola 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) 

Sarebbe essere utilizzato in questo modo:

for chunk in chunked_queryset(SomeModel.objects.all(), 20): 
    # `chunk` is a queryset 
    for item in chunk: 
     # `item` is a SomeModel instance 
     pass 
Problemi correlati