2012-01-03 18 views
5

Estoy ejecutando una operación por lotes en todas las filas de una base de datos. Esto implica seleccionar cada modelo y hacer algo al respecto. Tiene sentido dividir esto en pedazos y hacerlo por partes.¿La mejor manera de procesar la base de datos en fragmentos con Django QuerySet?

Actualmente estoy usando Paginator, porque es conveniente. Esto significa que necesito un pedido de los valores para que puedan ser ordenados en orden. Esto genera sentencias SQL que tienen las cláusulas order y limit, y para cada fragmento creo que Postgres puede clasificar la tabla completa (aunque no puedo afirmar que tenga ningún conocimiento sobre las internas). Todo lo que sé es que la base de datos está en alrededor del 50% de CPU y creo que es demasiado alta para estar haciendo select s.

¿Cuál es la mejor manera de iterar en toda la tabla de manera RDMBS/CPU?

Suponiendo que el contenido de la base de datos no cambia durante la operación por lotes.

Respuesta

5

De su descripción, realmente no se preocupa por el orden de clasificación de las filas que procesa. Si usted tiene las claves primarias de las tablas (que espero!), Este método crudo de partición sería mucho más rápido:

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

Se realiza la misma para cualquier desplazamiento y (casi) el mismo para cualquier tamaño de mesa Recuperar valores mínimo y máximo de la clave principal y la partición en consecuencia:

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

A diferencia:

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

Esto es generalmente más lento porque todas las filas tienen que ser ordenados y el rendimiento se degrada adicionalmente con mayores compensaciones y tablas más grandes.

+0

Esto supone que los registros se devuelven en el mismo orden y sin una 'sort' cláusula. ¿Es esto correcto? Además, si tengo una clasificación predeterminada en mi clase 'Meta', ¿puedo eliminarla de alguna manera para la consulta? – Joe

+0

@Joe: Básicamente obtienes los mismos registros, pero no ordenados. Si hay espacios en su espacio de ID, la cantidad de registros devueltos puede ser menor de lo esperado para cada llamada. Mientras que con LIMIT/OFFSET obtienes un número fijo de filas ordenadas (excepto la última llamada por tabla). No sé cómo manejar la clase 'Meta', pero * necesita * ordenar sus filas para LIMIT/OFFSET. –

+0

Erwin, lamento mucho no haber leído su respuesta correctamente. ¿Estás seguro de que esto es más rápido? la clausula 'between' seguramente solo puede funcionar si los identificadores ya están ordenados, o si realiza un escaneo completo de la tabla cada vez? – Joe

2

El código siguiente implementa la respuesta de Erwin anteriormente (usando BETWEEN) para un Django QuerySet:

Una función de utilidad que va a hacer esto para un arbitraria Django QuerySet es como sigue. Por defecto asume que 'id' es un campo adecuado para usar para la cláusula 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) 

Sería utilizado como esto:

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