DatabaseError: la transaction en cours est abandonnée, les commandes ignorées jusqu'à la fin du bloc de transaction?

252

J'ai eu beaucoup d'erreurs avec le message:

"DatabaseError: current transaction is aborted, commands ignored until end of transaction block"

après être passé de python-psycopg à python-psycopg2 en tant que moteur de base de données du projet Django.

Le code reste le même, mais je ne sais pas d'où viennent ces erreurs.

jack
la source
2
Je suis curieux de savoir quelle a été votre résolution finale à ce problème? J'ai ce même problème, mais comme mon hébergeur ne consigne pas les erreurs de requête, il a été impossible jusqu'à présent de comprendre ce qui ne va pas.
gerdemb
2
J'ai finalement suivi mon problème jusqu'à un bug lors de l'utilisation d'une table de base de données comme backend de cache. Bug Django: code.djangoproject.com/ticket/11569 Discussion StackOverflow: stackoverflow.com/questions/1189541/…
gerdemb
7
FYI Si vous utilisez simplement psycopg2 sans django, conn.rollback()(où conn est votre objet de connexion) effacera l'erreur afin que vous puissiez exécuter d'autres requêtes
User

Réponses:

177

C'est ce que fait postgres lorsqu'une requête produit une erreur et que vous essayez d'exécuter une autre requête sans d'abord annuler la transaction. (Vous pouvez le considérer comme une fonction de sécurité, pour vous empêcher de corrompre vos données.)

Pour résoudre ce problème, vous devez déterminer où dans le code cette mauvaise requête est exécutée. Il peut être utile d'utiliser les options log_statement et log_min_error_statement dans votre serveur postgresql.

ʇsәɹoɈ
la source
le problème est lorsque j'utilisais python-psycopg, aucune erreur de ce type n'a été signalée. psycopg2 a-t-il implémenté un mécanisme différent pour parler aux postgres?
jack
4
La méthode de conversation avec le serveur n'a probablement pas d'importance, mais il est possible que la version que vous avez utilisée auparavant soit par défaut en mode autocommit alors que la nouvelle version ne l'est pas. L'erreur s'est peut-être encore produite, mais vous auriez pu la manquer plus facilement. Il est également possible que la conversion du type de données ou autre chose ait changé depuis l'ancienne version. Quoi qu'il en soit, la meilleure solution consiste à rechercher la mauvaise requête afin que vous puissiez voir ce qui ne va pas.
ʇsәɹoɈ
133

Pour supprimer l'erreur, annulez la dernière transaction (erronée) après avoir corrigé votre code:

from django.db import transaction
transaction.rollback()

Vous pouvez utiliser try-except pour éviter que l'erreur ne se produise:

from django.db import transaction, DatabaseError
try:
    a.save()
except DatabaseError:
    transaction.rollback()

Voir: documentation Django

Anuj Gupta
la source
3
Cela résout le problème principal et vous permet de récupérer après une instruction qui a provoqué la transaction abandonnée.
RichVel
ceci, combiné avec try / except.
tomwolber
3
Pourquoi utiliser IntegrityErroret non la classe de base DatabaseError?
Jonathan
Pour une raison quelconque, j'ai dû déplacer la restauration en dehors de la section "sauf". J'utilisais .bulk_create () et non .save ()
nu everest
A travaillé avec django 1.4.16 après avoir suivi ce stackoverflow.com/a/15753000/573034
Paolo
50

Donc, je suis tombé sur ce même problème. Le problème que je rencontrais ici était que ma base de données n'était pas correctement synchronisée. Les problèmes simples semblent toujours provoquer le plus d'angoisse ...

Pour synchroniser votre base de données django, à partir de votre répertoire d'application, dans le terminal, tapez:

$ python manage.py syncdb

Edit: Notez que si vous utilisez django-south, l'exécution de la commande '$ python manage.py migrate' peut également résoudre ce problème.

Bon codage!

Michael Merchant
la source
3
A voté pour avoir énoncé l'évidence. Je ne donnerais pas plus d'un vote positif, car ce n'était probablement pas la réponse recherchée.
Jameson Quinn
5
Je l'ai corrigé de la même manière en python manage.py migrate <app>... pour toutes mes applications.
Clayton
3
@Clayton - vous ne dites pas, mais je suppose que vous utilisez django-south - la migratecommande n'est pas intégrée à django.
Greg Ball
@ GregBall- C'est exact ... J'utilise django-south. Désolé de ne pas avoir précisé.
Clayton
J'obtiens cette erreur en faisant syncdb - je pense que cela a à voir avec l'ordre que django parcourt les tables.
Stuart Axon
36

Dans Flask, il vous suffit d'écrire:

curs = conn.cursor()
curs.execute("ROLLBACK")
conn.commit()

La documentation PS va ici https://www.postgresql.org/docs/9.4/static/sql-rollback.html

Dmytro Lopushanskyy
la source
Cette solution est également très utile lorsque l'erreur se produit dans un ordinateur portable Jupyter.
Skippy le Grand Gourou
Agréable. Cela m'a aidé à Jupyter
igorkf
34

D'après mon expérience, ces erreurs se produisent de cette façon:

try:
    code_that_executes_bad_query()
    # transaction on DB is now bad
except:
    pass

# transaction on db is still bad
code_that_executes_working_query() # raises transaction error

Il n'y a rien de mal avec la deuxième requête, mais comme l'erreur réelle a été détectée, la deuxième requête est celle qui déclenche l'erreur (beaucoup moins informative).

edit: cela ne se produit que si la exceptclause intercepte IntegrityError(ou toute autre exception de base de données de bas niveau), si vous interceptez quelque chose comme DoesNotExistcette erreur ne se produira pas, car DoesNotExistcela ne corrompra pas la transaction.

La leçon ici est de ne pas essayer / sauf / passer.

prêtre
la source
16

Je pense que le modèle que Priest mentionne est plus susceptible d'être la cause habituelle de ce problème lors de l'utilisation de PostgreSQL.

Cependant, je pense qu'il existe des utilisations valides pour le modèle et je ne pense pas que ce problème devrait être une raison de toujours l'éviter. Par exemple:

try:
    profile = user.get_profile()
except ObjectDoesNotExist:
    profile = make_default_profile_for_user(user)

do_something_with_profile(profile)

Si vous vous sentez bien avec ce modèle, mais que vous souhaitez éviter le code de gestion de transaction explicite partout, vous pouvez envisager d'activer le mode de validation automatique (PostgreSQL 8.2+): https://docs.djangoproject.com/en/ dev / ref / databases / # mode autocommit

DATABASES['default'] = {
    #.. you usual options...
    'OPTIONS': {
        'autocommit': True,
    }
}

Je ne sais pas s'il existe des considérations de performances importantes (ou de tout autre type).

Sébastien
la source
6

Si vous obtenez cela dans le shell interactif et avez besoin d'une solution rapide, procédez comme suit:

from django.db import connection
connection._rollback()

vu à l'origine dans cette réponse

tutuDajuju
la source
6

J'ai rencontré un comportement similaire lors de l'exécution d'une transaction défectueuse sur le postgresterminal. Rien ne s'est passé après cela, car le databaseest dans un état de error. Cependant, juste comme une solution rapide, si vous pouvez vous permettre d'éviter rollback transaction. La suite a fait l'affaire pour moi:

COMMIT;

faizanjehangir
la source
J'étais dans une réponse, c'est exactement la réponse que je cherchais.
Sarink
5

J'ai le problème de Silimar. La solution était de migrer la base de données ( manage.py syncdbou manage.py schemamigration --auto <table name>si vous utilisez le sud).

Daniil Ryzhkov
la source
5

utilisez simplement la restauration

Exemple de code

try:
    cur.execute("CREATE TABLE IF NOT EXISTS test2 (id serial, qa text);")
except:
    cur.execute("rollback")
    cur.execute("CREATE TABLE IF NOT EXISTS test2 (id serial, qa text);")
Umer
la source
1

J'ai juste eu cette erreur aussi, mais elle masquait un autre message d'erreur plus pertinent où le code essayait de stocker une chaîne de 125 caractères dans une colonne de 100 caractères:

DatabaseError: value too long for type character varying(100)

J'ai dû déboguer le code pour que le message ci-dessus apparaisse, sinon il affiche

DatabaseError: current transaction is aborted
Thierry Lam
la source
1

En réponse à @priestc et @Sebastian, que faire si vous faites quelque chose comme ça?

try:
    conn.commit()
except:
    pass

cursor.execute( sql )
try: 
    return cursor.fetchall()
except: 
    conn.commit()
    return None

Je viens d'essayer ce code et il semble fonctionner, échouant silencieusement sans avoir à se soucier d'éventuelles erreurs et fonctionnant lorsque la requête est bonne.

Nate
la source
1

Je crois que la réponse de @ AnujGupta est correcte. Cependant, la restauration peut elle-même déclencher une exception que vous devez intercepter et gérer:

from django.db import transaction, DatabaseError
try:
    a.save()
except DatabaseError:
    try:
        transaction.rollback()
    except transaction.TransactionManagementError:
        # Log or handle otherwise

Si vous constatez que vous réécrivez ce code à divers save()endroits, vous pouvez extraire la méthode:

import traceback
def try_rolling_back():
    try:
        transaction.rollback()
        log.warning('rolled back')  # example handling
    except transaction.TransactionManagementError:
        log.exception(traceback.format_exc())  # example handling

Enfin, vous pouvez le peaufiner en utilisant un décorateur qui protège les méthodes qui utilisent save():

from functools import wraps
def try_rolling_back_on_exception(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        try:
            return fn(*args, **kwargs)
        except:
            traceback.print_exc()
            try_rolling_back()
    return wrapped

@try_rolling_back_on_exception
def some_saving_method():
    # ...
    model.save()
    # ...

Même si vous implémentez le décorateur ci-dessus, il est toujours pratique de le conserver try_rolling_back()comme méthode extraite au cas où vous auriez besoin de l'utiliser manuellement dans les cas où une manipulation spécifique est requise, et la manipulation générique du décorateur n'est pas suffisante.

Jonathan
la source
1

C'est un comportement très étrange pour moi. Je suis surpris que personne ne pense aux points de sauvegarde. Dans mon code, une requête ayant échoué était un comportement attendu:

from django.db import transaction
@transaction.commit_on_success
def update():
    skipped = 0
    for old_model in OldModel.objects.all():
        try:
            Model.objects.create(
                group_id=old_model.group_uuid,
                file_id=old_model.file_uuid,
            )
        except IntegrityError:
            skipped += 1
    return skipped

J'ai changé le code de cette façon pour utiliser les points de sauvegarde:

from django.db import transaction
@transaction.commit_on_success
def update():
    skipped = 0
    sid = transaction.savepoint()
    for old_model in OldModel.objects.all():
        try:
            Model.objects.create(
                group_id=old_model.group_uuid,
                file_id=old_model.file_uuid,
            )
        except IntegrityError:
            skipped += 1
            transaction.savepoint_rollback(sid)
        else:
            transaction.savepoint_commit(sid)
    return skipped
homm
la source
1

Dans Flask shell, tout ce que je devais faire était session.rollback()de surmonter cela.

watsonic
la source
1

J'ai rencontré ce problème, l'erreur apparaît car les transactions d'erreur ne se sont pas terminées correctement, j'ai trouvé la postgresql_transactionscommande de Transaction Control ici

Contrôle des transactions

Les commandes suivantes sont utilisées pour contrôler les transactions

BEGIN TRANSACTION  To start a transaction.

COMMIT  To save the changes, alternatively you can use END TRANSACTION command.

ROLLBACK  To rollback the changes.

donc j'utilise END TRANSACTIONpour mettre fin à la TRANSACTION d'erreur, code comme ceci:

    for key_of_attribute, command in sql_command.items():
        cursor = connection.cursor()
        g_logger.info("execute command :%s" % (command))
        try:
            cursor.execute(command)
            rows = cursor.fetchall()
            g_logger.info("the command:%s result is :%s" % (command, rows))
            result_list[key_of_attribute] = rows
            g_logger.info("result_list is :%s" % (result_list))
        except Exception as e:
            cursor.execute('END TRANSACTION;')
            g_logger.info("error command :%s and error is :%s" % (command, e))
    return result_list
Dean Fang
la source
-6

vous pouvez désactiver la transaction via "set_isolation_level (0)"

springrider
la source