Take the 2-minute tour ×
Database Administrators Stack Exchange is a question and answer site for database professionals who wish to improve their database skills and learn from others in the community. It's 100% free, no registration required.

Just trying to understand the thinking here...

This bug explains that with other databases, you are allowed to run additional commands after a SQLException occurs. It was written in 2001 but has never been touched. We're working with Postgres and SQLServer, and so now we're bumping into this issue.

Suppose you have:

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);

Can anyone help me understand why Postgres disallows this? Or even better, is there a connection flag to change the behavior? (I looked and could not find anything.)

Thanks in advance for any advice!

share|improve this question
    
What's your client? Which flags do you use when startint it? Postgres version? –  dezso Oct 31 '12 at 16:04
    
In a quick test alongside the code above I don't get the behaviour you described. Could you include the actual error messages? –  dezso Oct 31 '12 at 16:10
    
This is a Java app, so we're using the PG 9.0 JDBC driver. I have tested the above through a query tool using the same driver to take our application out of the picture. The second insert statement above yields: ERROR: current transaction is aborted, commands ignored until end of transaction block [SQL State: 25P02] –  DaveyBob Oct 31 '12 at 16:13
add comment

1 Answer

The problem I have with your example is that you're talking about JDBC behaviour, but also using explicit "start transaction" etc commands, which seems a bit of a clash, since I'd expect you'd use JDBC's auto-commit mode to manage transactions.

If you are in auto-commit mode, then the two inserts will each be in their own transaction, and the throw of a SQLException for the first one will not affect the second.

If you are not in auto-commit mode, then an implicit "start transaction" is generated before the first insert, and the second insert cannot be processed until the transaction is rolled back. This behaviour is quite different from if you were executing the script with psql.

(JDBC does not specify whether drivers/connections should default to auto-commit on or off, you should always explicitly set it)

Postgresql treats any error processing a statement as immediately aborting the transaction-- essentially like the XACT_ABORT mode in SQL Server. The intent being that if you submit a sequence of commands as a transaction, each one is dependent on the previous ones, so the failure of any one invalidates all the subsequent ones.

If this isn't the behaviour you want inside a transaction, you need to surround the potentially-aborting updates with creating a savepoint, and rolling back to that savepoint in case of an error.

Beware of looking at very old discussions of behaviour (bugs over ten years old definitely count), as at some point in Postgresql's history, there was a session variable called autocommit, and the behaviour could have been quite different. That variable is gone now, replaced (as I understand it) with the concepts of the database or the JDBC driver automatically wrapping commands inside transactions (so in fact there is not really any such thing as non-transactional interaction with postgresql).

Here is what happens when you execute the script you suggest with 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

In order to get the same behaviour as you wrote in the script, you'd have to turn off auto-commit before doing the insert- that stops the JDBC driver from issuing an implicit "start transaction" before it executes the next statement. If you put that implicitly-generated transaction into the psql script, it produces the error you describe:

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

As an illustration of why this behaviour exists, consider what happens if I run the first transaction again. The intent is "create the table and populate it with a single row":

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

So as soon as a problem is detected ("test" already exists), the remaining data manipulation isn't appropriate (the row already existed too, anyway)

share|improve this answer
1  
Hi Steve, Thanks for the info! I realize I was kind of mixing things here...I added the "start transaction" bit to indicate that it was not an auto-commit tx. We started down the road of using SavePoints to achieve consistent behavior across the databases we use. Might have to go down that road after all. –  DaveyBob Oct 31 '12 at 16:20
    
@DaveyBob Ah, then the JDBC driver will be adding an implicit "start transaction" before the first "insert" as well. I've added some more explanation. –  araqnid Oct 31 '12 at 16:35
1  
@DaveyBob: Would you mind updating the question with the full information? Would make a lot more sense to other people then. (Aka: "Don't hide important information in comments!") –  Erwin Brandstetter Oct 31 '12 at 19:25
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.