74 votes

Comment forcer Django à ignorer les caches et à recharger les données?

Je suis en utilisant le Django modèles de base de données à partir d'un processus qui n'est pas appelée à partir d'une requête HTTP. Le processus est censé sondage pour les nouvelles données en quelques secondes et faire un peu de traitement. J'ai une boucle qui dort pendant quelques secondes, puis il reçoit toutes les données non gérée à partir de la base de données.

Ce que je vois, c'est que, après la première extraction, le processus ne voit jamais de nouvelles données. J'ai couru quelques tests et il semble que Django est la mise en cache des résultats, même si je suis en train de construire de nouveaux QuerySets à chaque fois. Pour vérifier cela, je l'ai fait à partir d'une interface Python:

>>> MyModel.objects.count()
885
# (Here I added some more data from another process.)
>>> MyModel.objects.count()
885
>>> MyModel.objects.update()
0
>>> MyModel.objects.count()
1025

Comme vous pouvez le voir, l'ajout de nouvelles données ne change pas le résultat de comptage. Toutefois, appeler le gestionnaire de la méthode update() semble résoudre le problème.

Je ne trouve pas de documentation sur la méthode update() et n'ont aucune idée de ce que d'autres mauvaises choses, il peut le faire.

Ma question est, pourquoi suis-je en voyant ce comportement de mise en cache, ce qui contredit ce que Django docs dire? Et comment puis-je l'empêcher de se produire?

93voto

Nick Craig-Wood Points 18742

Ayant eu ce problème et trouvé deux solutions définitives pour cela j'ai pensé qu'il vaut la peine de poster une autre réponse.

C'est un problème avec MySQL par défaut du mode de transaction. Django ouvre une transaction au début, ce qui signifie que, par défaut, vous ne verrez pas les changements effectués dans la base de données.

Démontrer comme ceci

Exécuter un django shell du terminal 1

>>> MyModel.objects.get(id=1).my_field
u'old'

Et un autre dans le terminal 2

>>> MyModel.objects.get(id=1).my_field
u'old'
>>> a = MyModel.objects.get(id=1)
>>> a.my_field = "NEW"
>>> a.save()
>>> MyModel.objects.get(id=1).my_field
u'NEW'
>>> 

Retour au terminal 1 pour démontrer le problème - nous encore lire l'ancienne valeur de la base de données.

>>> MyModel.objects.get(id=1).my_field
u'old'

Maintenant dans le terminal 1 de démontrer la solution

>>> from django.db import transaction
>>> 
>>> @transaction.commit_manually
... def flush_transaction():
...     transaction.commit()
... 
>>> MyModel.objects.get(id=1).my_field
u'old'
>>> flush_transaction()
>>> MyModel.objects.get(id=1).my_field
u'NEW'
>>> 

Les nouvelles données sont maintenant lire

Ici, c'est que le code dans un format facile à coller bloc avec docstring

from django.db import transaction

@transaction.commit_manually
def flush_transaction():
    """
    Flush the current transaction so we don't read stale data

    Use in long running processes to make sure fresh data is read from
    the database.  This is a problem with MySQL and the default
    transaction mode.  You can fix it by setting
    "transaction-isolation = READ-COMMITTED" in my.cnf or by calling
    this function at the appropriate moment
    """
    transaction.commit()

La solution alternative est de changer mon.cnf pour MySQL pour modifier la valeur par défaut du mode de transaction

transaction-isolation = READ-COMMITTED

Note que c'est un élément relativement nouveau pour Mysql et a des conséquences pour le binaire d'enregistrement / d'asservissement. Vous pouvez également mettre cela dans le django de connexion préambule si vous le souhaitez.

8voto

hwjp Points 3041

Nous avons lutté un peu juste avec un forçage de django pour actualiser le "cache" - qui s'avère n'était pas vraiment un cache à tous, mais un artefact dû à des transactions. Cela pourrait ne pas s'appliquer à votre exemple, mais certainement dans django points de vue, par défaut, il y a un appel implicite à une opération, mysql puis les isolats provenant de tous les changements qui se produisent à partir d'autres processus ater de commencer.

nous avons utilisé l' @transaction.commit_manually décorateur et des appels à l' transaction.commit() juste avant chaque occasion où vous avez besoin d'up-to-date information.

Comme je l'ai dit, cela s'applique certainement à la vue, vous ne savez pas s'il devait s'appliquer à django code pas en cours d'exécution à l'intérieur d'un point de vue.

les informations détaillées ici:

http://devblog.resolversystems.com/?p=439

6voto

adamJLev Points 5892

Semble que le count() va à cache après la première fois. C'est le django source de QuerySet.le comte:

def count(self):
    """
    Performs a SELECT COUNT() and returns the number of records as an
    integer.

    If the QuerySet is already fully cached this simply returns the length
    of the cached results set to avoid multiple SELECT COUNT(*) calls.
    """
    if self._result_cache is not None and not self._iter:
        return len(self._result_cache)

    return self.query.get_count(using=self.db)

update ne semble pas être à faire un peu de travail supplémentaire, en plus de ce que vous avez besoin.
Mais je ne peux pas penser à une meilleure façon de le faire, à court d'écrire votre propre code SQL pour le comte.
Si la performance n'est pas super important, je voudrais juste faire ce que vous faites, appelant update avant count.

QuerySet.mise à jour:

def update(self, **kwargs):
    """
    Updates all elements in the current QuerySet, setting all the given
    fields to the appropriate values.
    """
    assert self.query.can_filter(), \
            "Cannot update a query once a slice has been taken."
    self._for_write = True
    query = self.query.clone(sql.UpdateQuery)
    query.add_update_values(kwargs)
    if not transaction.is_managed(using=self.db):
        transaction.enter_transaction_management(using=self.db)
        forced_managed = True
    else:
        forced_managed = False
    try:
        rows = query.get_compiler(self.db).execute_sql(None)
        if forced_managed:
            transaction.commit(using=self.db)
        else:
            transaction.commit_unless_managed(using=self.db)
    finally:
        if forced_managed:
            transaction.leave_transaction_management(using=self.db)
    self._result_cache = None
    return rows
update.alters_data = True

-1voto

Travis Swicegood Points 324

Vous pouvez également utiliser MyModel.objects._clone().count(). Toutes les méthodes du du QuerySet appel _clone() avant tout travail - qui assure que les caches internes sont invalidés.

La cause première est que MyModel.objects est la même instance à chaque fois. En le clonant, vous créez une nouvelle instance sans la valeur mise en cache. Bien sûr, vous pouvez toujours accéder au cache et l'invalider si vous préférez utiliser la même instance.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X