2009-12-05 17 views
8

Tengo un modelo llamado Valor. Valor tiene un robot. Estoy consultando de esta manera:búsqueda rápida para el último elemento en un Django QuerySet?

Valor.objects.filter(robot=r).reverse()[0] 

para obtener el último robot Valor the r. Valor.objects.filter (robot = r) .count() es aproximadamente 200000 y obtener los últimos elementos lleva aproximadamente 4 segundos en mi PC.

¿Cómo puedo acelerarlo? Estoy preguntando de la manera incorrecta?

+0

¿Tiene una estructura muy adornado de las relaciones ForeignKey, OneToOneField o ManyToManyField? –

+0

fwiw, esto es lento porque está seleccionando todo en la tabla 'valor', instanciando una instancia de modelo django para cada entrada cuando la convierte a una lista (a través de' .reverse'), y tomando solo el primer elemento en el lista. – Carson

Respuesta

3

Parece que su conjunto de datos va a ser lo suficientemente grande como para querer desnormalizar un poco las cosas. ¿Has intentado hacer un seguimiento del último objeto Valor en el objeto Robot?

class Robot(models.Model): 
    # ... 
    last_valor = models.ForeignKey('Valor', null=True, blank=True) 

y luego usar un post_savesignal para realizar la actualización.

from django.db.models.signals import post_save 

def record_last_valor(sender, **kwargs): 
    if kwargs.get('created', False): 
     instance = kwargs.get('instance') 
     instance.robot.last_valor = instance 

post_save.connect(record_last_valor, sender=Valor) 

Va a pagar el costo de una transacción db adicional al crear los objetos de Valor pero la consulta del last_valor estará ardiendo rápido. Juega con él y ve si la compensación vale la pena para tu aplicación.

+1

Esta es una buena solución para el problema de diseño, pero deja abierta la pregunta de por qué su diseño tuvo un rendimiento pobre incluso cuando se le dieron consultas más apropiadas que su original. Con una indexación, ordenamiento y límites correctos, su diseño original normalizado también debería haber sido "extremadamente rápido". Espero que el OP actualice la pregunta con sus hallazgos. –

+0

Django me pidió que cambiarlo a: record_last_valor def (remitente, ** kwargs): si creó: instance.robot.last_valor = instancia –

+0

Estoy de acuerdo Joe, esto no trata de responder a los problemas de rendimiento de base de datos subyacentes. Parecía que ya se hablaba mucho sobre la comprobación de índices y su sugerencia sobre qs.query parecía la mejor manera posible de investigar el enfoque de los índices de verificación. – istruble

3

Bueno, no hay una cláusula order_by así que me pregunto qué quiere decir con 'last'. Suponiendo que haya querido decir "último agregado",

Valor.objects.filter(robot=r).order_by('-id')[0] 

podría hacer el trabajo por usted.

+0

En el modelo de Valor tengo Meta: ordering = ('id',). ¿Esto hace que tu consulta sea la misma que la mía? –

+0

He intentado su solución y es más lenta que la mía :( –

+0

¿Tiene un índice apropiado en su tabla de valor para robot_id? –

0

¿Existe una cláusula de límite en django? De esta forma puedes tener el db, simplemente devuelve un solo registro.

MySQL

select * from table where x = y limit 1 

servidor SQL

select top 1 * from table where x = y 

oráculo

select * from table where x = y and rownum = 1 

Sé que esto no se traduce en Django, pero alguien puede volver y limpiar esto.

+0

limite y reversa, quiere decir. ¿No? –

+0

En Django usted limita con el sintaxis de corte de Python. Valor.objetos.filtro (robot = r) .reverse() [0: 1] .get() –

+0

Acabo de intentar esa última consulta y es tan lenta como la original. –

7

Si ninguna de las sugerencias anteriores funciona, sugiero eliminar a Django de la ecuación y ejecutar este sql en bruto en su base de datos. Supongo que en sus nombres de tabla, por lo que puede que tenga que ajustar en consecuencia:

SELECT * FROM valor v WHERE v.robot_id = [robot_id] ORDER BY id DESC LIMIT 1; 

¿Eso es lenta? Si es así, haga que su RDBMS (¿MySQL?) Le explique el plan de consulta. Esto le indicará si está realizando escaneos completos de tablas, lo que obviamente no quiere con una tabla tan grande. También puede editar su pregunta e incluir el esquema de la tabla valor para que podamos verla.

Además, se puede ver el SQL que Django está generando al hacer esto (utilizando el criterio de consulta proporcionado por Peter Rowell):

qs = Valor.objects.filter(robot=r).order_by('-id')[0] 
print qs.query 

Asegúrese de que SQL es similar a la consulta 'en bruto' que he publicado encima. También puede hacer que su RDBMS le explique ese plan de consulta.

+1

qs.query (o .query.as \ _sql()) es definitivamente el camino a seguir para cualquiera que intente rastrear este problema desde el lado de la base de datos. Las personas que se preguntan cómo cortar/límite de sql, order_by() y filter() son manejado en un nivel bajo debería jugar con esto un poco. – istruble

7

La sintaxis MySQL óptima para este problema sería algo a lo largo de las líneas de:

SELECT * FROM table WHERE x=y ORDER BY z DESC LIMIT 1 

El Django equivalente de esto sería:

Valor.objects.filter(robot=r).order_by('-id')[:1][0] 

Aviso cómo esta solución utiliza el método de Django slicing a limite el conjunto de consulta antes de compilando la lista de objetos.

1

bastante rápido también debería ser:

qs = Valor.objects.filter(robot=r) # <-- it doesn't hit the database 
count = qs.count()     # <-- first hit the database, compute a count 
last_item = qs[ count-1 ]   # <-- second hit the database, get specified rownum 

Por lo tanto, en la práctica se ejecuta sólo 2 consultas SQL;)

0

La forma correcta de hacer esto, es el uso de la incorporada en el método de QuerySet última() y alimentarlo cualquiera que sea la columna (nombre del campo) por la que debe ordenar. El inconveniente es que solo puede ordenar por una sola columna db.

La implementación actual se ve así y está optimizada en el mismo sentido que la sugerencia de @ Aaron.

def latest(self, field_name=None): 
    """ 
    Returns the latest object, according to the model's 'get_latest_by' 
    option or optional given field_name. 
    """ 
    latest_by = field_name or self.model._meta.get_latest_by 
    assert bool(latest_by), "latest() requires either a field_name parameter or 'get_latest_by' in the model" 
    assert self.query.can_filter(), \ 
      "Cannot change a query once a slice has been taken." 
    obj = self._clone() 
    obj.query.set_limits(high=1) 
    obj.query.clear_ordering() 
    obj.query.add_ordering('-%s' % latest_by) 
    return obj.get() 
0
Model_Name.objects.first() 

// Para obtener primero elemento

Model_name.objects.last() 

// Para obtener la última()

en mi caso el pasado no es trabajo, porque sólo hay una fila en la base de datos pueden ser de ayuda completa para u también :)

Cuestiones relacionadas