15

Postgres automatically aborts transactions whenever any SQL statement terminates with an error, which includes any constraint violation. For example:

glyph=# create table foo (bar integer, constraint blug check(bar > 5));
CREATE TABLE
glyph=# begin;
BEGIN
glyph=# insert into foo values (10);
INSERT 0 1
glyph=# insert into foo values (1);
ERROR:  new row for relation "foo" violates check constraint "blug"
STATEMENT:  insert into foo values (1);
ERROR:  new row for relation "foo" violates check constraint "blug"

No message has yet been issued to that effect, but the transaction is rolled back. My personal favorite line of this session is the following:

glyph=# commit;
ROLLBACK

... since "ROLLBACK" seems like an odd success-message for COMMIT. But, indeed, it's been rolled back, and there are no rows in the table:

glyph=# select * from foo;
 bar 
-----
(0 rows)

I know that I can create a ton of SAVEPOINTs and handle errors in SQL that way, but that involves more traffic to the database, more latency (I might have to handle an error from the SAVEPOINT after all), for relatively little benefit. I really just want to handle the error in my application language anyway (Python) with a try/except, so the only behavior I want out of the SQL is for errors not to trigger automatic rollbacks. What can I do?

Ricardo Cárdenes
  • 9,004
  • 1
  • 21
  • 34
Glyph
  • 31,152
  • 11
  • 87
  • 129
  • Well, if you don't care about constraint violations, drop the constraint or don't use transactions and insert your stuff with autocommit. – tscho Feb 24 '12 at 20:02
  • 1
    I do care about the constraint violations. I want the constraint to be checked, and if it's violated I do want to get the notification that that happened at the application level, so that I can do something different. I just want only that individual statement to fail, not the whole transaction. Part of the problem is that I want to support multiple databases, and Postgres's behavior seems to be inconsistent with most other DBs (MySQL, Oracle, and SQLite as far as I can tell just skimming docs). – Glyph Feb 24 '12 at 23:15
  • 1
    possible duplicate of [Can I ask Postgresql to ignore errors within a transaction](http://stackoverflow.com/questions/2741919/can-i-ask-postgresql-to-ignore-errors-within-a-transaction) – A.H. Feb 24 '12 at 23:36
  • I don't think this is a duplicate, because (A) I don't want to ignore errors, I just want to handle errors without rollback, and (B) that appears to be a question about the `psql` command-line tool, not postgres itself. – Glyph Mar 02 '12 at 17:18
  • Transaction has TWO common use cases in practical:1.It used to ensure ACID, in this case, abort sub-sequence statements is ok. But 2.It used to reduce the disk flush times for some BATCH ops. In this case we just use Transaction to optimize the performance, and don't want DBMS do the auto abort. So PostgreSQL don't support the second use case, right? – ASBai Nov 19 '16 at 17:40
  • 1
    Keep your transactions small, maybe? – wildplasser Sep 15 '17 at 00:12
  • 1
    The definition of a transaction is: "either **all** statements succeed or **none**" - apparently you do not want a transaction spanning all inserts. so use auto-commit and then each insert is its own transaction –  Sep 15 '17 at 10:54

3 Answers3

2

I'm extremely new to PostgreSQL, but one of the examples in the PostgreSQL documentation for triggers / server-side programming looks like it does exactly what you're looking for.

See: http://www.postgresql.org/docs/9.2/static/trigger-example.html

Snippet from the page: "So the trigger acts as a not-null constraint but doesn't abort the transaction."

  • This isn't *exactly* what I was looking for, but I think the specific structure I'm looking for is just impossible, and this is the closest thing that can be done. Thanks! – Glyph Jun 26 '22 at 04:28
1

I know this is a very old ticket but (as of 2017) PostgreSQL still have this same behavior of auto-rolling back itself when something goes wrong in the commit. I'd like to share some thoughts here.

I don't know if we can change this behavior, and I don't need this, maybe for the best of delegating PostgreSQL to manage the rollback for us (he knows what he is doing, right ?). Rolling back means changing the data back to its original state before the failed transaction, that means altered or inserted data from triggers will also be discarded. In an ACID logic, this is what we want. Let say you are managing the rollback on the back-end yourself, if something goes wrong during your custom rollback or if the database is changed at the same time from external transactions during your rollback, the data becomes inconsistent and your whole structure most likely to collapse.

So knowing that PostgreSQL will manage its own rollback strategy, the question to ask is "how can I extend the rollback strategy ?". The thing you first should think of is "what caused the transaction to fail ?". In your try/catch structure, try to handle all the possible exceptions and run the transaction again or send feedback to the front-end application with some appropriate "don't do" messages. For me, this is the best way of handling things, it is less code, less overhead, more control, more user-friendly and your database will thank you.

A last point I want to shed light on, SQL standard is having a sqlstate code that can be use to communicate with back-end modules. The failing operation during a transaction will return a sqlstate code, you can then use these codes to make appropriate drawbacks. You can make your own sqlstate codes, as long as it doesn't mess with the reserved ones (https://www.postgresql.org/message-id/20185.1359219138%40sss.pgh.pa.us). For instance in a plpgsql function

...
$$
begin
...do something...if it goes wrong
raise exception 'custom exception message' using errcode='12345';
end
$$
...

This is a example using PDO in PHP (using the error code above) :

...
$pdo->beginTransaction();
try {
  $s = $pdo->prepare('...');
  $s->execute([$value]);

  /**
   * Simulate a null violation exception
   * If it fails, PDO will not wait the commit
   * and will throw the exception, the code below
   * is not executed.
   */
  $s->execute([null]);

  /**
   * If nothing wrong happened, we commit to change
   * the database state.
   */
  $pdo->commit();
}
catch (PDOException $e) {
  /**
   * It is important to also have the commit here.
   * It will trigger PostgreSQL rollback.
   * And make the pdo Object back to auto-commit mode.
   */
  $pdo->commit();

  if ($e->getCode() === '12345') {
    send_feedback_to_client('please do not hack us.');
  }
}
...
vdegenne
  • 12,272
  • 14
  • 80
  • 106
0

I would strongly suggest SqlAlchemy and use subtransactions. You can code like:

#some code 
Session.begin(subtransactions=True)
#more stuff including sql activity then:
with Session.begin(nested=True):
    # get the security
    try:
       foo = MyCodeToMakeFOO(args)
       Session.add(foo)
       Session.flush()
    except:
       log.error("Database hated your foo(%s) but I'll do the rest"%foo)

Most useful when the subtransaction is in a loop where you want to process the good records and log the bad ones.

Phil Cooper
  • 5,747
  • 1
  • 25
  • 41
  • I've never used Python (I've seen it though) let alone SqlAlchemy, but in your example, what's the point in having a transaction at all here? Why not just insert stuff without a transaction with autocommit when you do not want all-or-nothing transaction semantics? – tscho Feb 24 '12 at 20:10
  • in the tiny sample here it might not matter but in more complex "real world" examples you might process a securities transaction in multiple steps (matching, price checking, order allocation). Each step could have database implications and if there is an error in one trade in a file of hundreds you don't want a half processed execution.(the A in [ACID](http://en.wikipedia.org/wiki/ACID)) – Phil Cooper Feb 24 '12 at 20:39
  • I specifically said that I knew I could use `SAVEPOINT`s, which are effectively the implementation mechanism for subtransactions, but they consume database and network resources I'd prefer to avoid consuming for this use-case if I can. (In fact, I've implemented my own asynchronous sqlalchemy-alike thing with even fancier sub-transaction support.) – Glyph Feb 24 '12 at 21:23
  • @tscho - the specific transaction I'm handling is a complex multi-party interaction where partial failure (specifically the type of failure modeled by a violated constraint) is perfectly acceptable. The whole thing does have to be in a transaction, for many of the same reasons Phil Cooper described. – Glyph Feb 24 '12 at 21:24
  • AFAIK, If you are in a transaction, once you pass an error generating command to the DB, the only thing it can do is rollback to a savepoint or the begining of the transaction. I'd still checkout SQLAlchemy because it does it's own magic and database write (and SAVEPOINT traffic) are not necessarily sent untll you `flush` or `commit`. If you have your own mechinism, the only way to know(?) which if faster is to benchmark. – Phil Cooper Feb 24 '12 at 21:38
  • 1
    @Glyph: There is no other way than SAVEPOINTs and EXCEPTION trapping in functions to get some kind of *nested transaction* semantics in PostgreSQL. I don't know what magic SqlAlchemy does to get this behavior. I am currently mostly working with Spring and Hibernate on the application level. When I don't want something to break a transaction, I implement it in a way that it does not cause a DB error (e.g. query if unique constraint would be violated before insert) or just don't execute it in that transaction but in another one. – tscho Feb 24 '12 at 22:57
  • The SqlAlchemy behavior I was referring to is simply that for some actions (especially ORM related activity) database writes are cached so if there are 5 "activities" and then an error before SqlAlchemy flushes to the DB, there is no insert,update,or savepoint transmitted to the db. – Phil Cooper Feb 24 '12 at 23:23
  • @PhilCooper: But how does SqlAlchemy know that there is a database error without going to the database? Application level exceptions won't break the transaction anyway (i.e. they *may* always be ignored). Or is SqlAlchemy creating a SAVEPOINT for each nested transaction and doing a ROLLBACK TO SAVEPOINT in case of a DB error? – tscho Feb 25 '12 at 11:24