web-dev-qa-db-fra.com

Postgres nécessite de commit ou de restauration après exception

J'essaie juste de comprendre la pensée ici ...

Ceci bug explique qu'avec d'autres bases de données, vous êtes autorisé à exécuter des commandes supplémentaires une fois qu'une sqlexception se produit. Il a été écrit en 2001 mais n'a jamais été touché. Nous travaillons avec Postgres et SQL Server, et nous sommes donc cognés dans ce numéro.

Supposons que vous ayez:

start transaction;
create table test(id int primary key);
insert into test values (1);
commit;

-- Following statement throws a SQLException(duplicate key) in
-- PG, SS and Oracle
insert into test values (1);

-- Following statement behaves differently for different DBMS:
-- SS and OR: No error...statement runs fine
-- PG: Another SQLException thrown...must rollback or commit
insert into test values (99);

Quelqu'un peut-il m'aider à comprendre pourquoi Postgres l'ignore? Ou même mieux, existe-t-il un drapeau de connexion pour changer le comportement? (J'ai regardé et je n'ai rien trouvé.)

J'ai ajouté la "transaction de démarrage" ci-dessus pour indiquer que ce n'était pas une auto-validation TX.

Ceci est un Java APP, nous utilisons donc le pilote PG 9.0 JDBC. J'ai testé ce qui précède via un outil de requête utilisant le même pilote pour prendre notre application hors de la photo. La seconde Déclaration d'insertion ci-dessus rendement:

Erreur: la transaction actuelle est abandonnée, les commandes ignorées jusqu'à la fin du bloc de transaction
[.____] [SQL State: 25P02]

3
DaveyBob

Le problème que j'ai avec votre exemple est que vous parlez de comportement JDBC, mais également en utilisant des commandes de "transaction de démarrage" explicites, qui semble un peu un affrontement, car je m'attendais à ce que vous utilisiez le mode de commit automatique de JDBC pour gérer les transactions.

Si vous êtes en mode de validation automatique, les deux inserts seront chacun dans leur propre transaction et que le lancer d'une sqléxception pour la première n'affecte pas la seconde.

Si vous n'êtes pas en mode de validation automatique, une "transaction de démarrage" implicite est générée avant la première insertion et la seconde insertion ne peut pas être traitée tant que la transaction est renvoyée. Ce comportement est assez différent de si vous exécutez le script avec PSQL.

(JDBC ne précise pas si les pilotes/connexions doivent être par défaut pour s'engager automatiquement ou éteindre, vous devez toujours la définir explicitement)

PostgreSQL traite toute erreur de traitement d'une instruction comme étant immédiatement avortement de la transaction - essentiellement comme la XACT_ABORT mode dans SQL Server. L'intention étant que si vous soumettez une séquence de commandes sous forme de transaction, chacune dépend de celle des précédents, de sorte que l'échec de l'un d'entre eux invalide tous les ultérieurs.

Si ce n'est pas le comportement que vous souhaitez à l'intérieur d'une transaction, vous devez entourer les mises à jour potentiellement avortées avec la création d'une note de sauvegarde et revenir à ce point de sauvegarde en cas d'erreur.

Méfiez-vous de regarder de très anciennes discussions sur le comportement (bugs de plus de dix ans compte définitivement), comme à un moment donné de l'histoire de PostgreSQL, une variable de session appelée autocommit et le comportement aurait pu être assez différent. Cette variable est partie maintenant, remplacée (comme je le comprends) avec les concepts de la base de données ou du pilote JDBC emballant automatiquement les commandes de l'intérieur des transactions (donc, il n'y a donc pas vraiment d'interaction non transactionnelle avec PostgreSQL).

Voici ce qui se passe lorsque vous exécutez le script que vous suggérez avec PSQL:

steve@steve@[local] =# start transaction;
START TRANSACTION
steve@steve@[local] *=# create table test(id int primary key);
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "test_pkey" for table "test"
CREATE TABLE
steve@steve@[local] *=# insert into test values (1);
INSERT 0 1
steve@steve@[local] *=# commit;
COMMIT
steve@steve@[local] =# 
steve@steve@[local] =# -- Following statement throws a SQLException(duplicate key) in
steve@steve@[local] =# -- PG, SS and Oracle
steve@steve@[local] =# insert into test values (1);
ERROR:  duplicate key value violates unique constraint "test_pkey"
DETAIL:  Key (id)=(1) already exists.
steve@steve@[local] =# 
steve@steve@[local] =# -- Following statement behaves differently for different DBMS:
steve@steve@[local] =# -- SS and OR: No error...statement runs fine
steve@steve@[local] =# -- PG: Another SQLException thrown...must rollback or commit
steve@steve@[local] =# insert into test values (99);
INSERT 0 1

Afin d'obtenir le même comportement que vous avez écrit dans le script, vous devez désactiver l'auto-commission avant de faire l'insertion - qui empêche le pilote JDBC d'émettre une "transaction de démarrage" implicite avant d'exécuter la prochaine déclaration. Si vous mettez cette transaction générée implicitement dans le script PSQL, elle produit l'erreur que vous décrivez:

steve@steve@[local] =# start transaction; -- generated by JDBC driver
START TRANSACTION
steve@steve@[local] *=# -- Following statement throws a SQLException(duplicate key) in
steve@steve@[local] *=# -- PG, SS and Oracle
steve@steve@[local] *=# insert into test values (1);
ERROR:  duplicate key value violates unique constraint "test_pkey"
DETAIL:  Key (id)=(1) already exists.
steve@steve@[local] !=# 
steve@steve@[local] !=# -- Following statement behaves differently for different DBMS:
steve@steve@[local] !=# -- SS and OR: No error...statement runs fine
steve@steve@[local] !=# -- PG: Another SQLException thrown...must rollback or commit
steve@steve@[local] !=# insert into test values (99);
ERROR:  current transaction is aborted, commands ignored until end of transaction block

À titre d'illustration de la raison pour laquelle ce comportement existe, considérons ce qui se passe si j'exécute la première transaction. L'intention est "Créer la table et le peupler avec une seule rangée":

steve@steve@[local] =# start transaction;
START TRANSACTION
steve@steve@[local] *=# create table test(id int primary key);
ERROR:  relation "test" already exists
steve@steve@[local] !=# insert into test values (1);
ERROR:  current transaction is aborted, commands ignored until end of transaction block
steve@steve@[local] !=# commit;
ROLLBACK

Donc, dès qu'un problème est détecté ("test" existe déjà), la manipulation de données restante n'est pas appropriée (la ligne existait déjà aussi, de toute façon))

6
araqnid