Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

- Begin transaction

- Do INSERT

- If you get a PK violation, do an UPDATE instead

- Commit

This is not safe in the default transaction mode, other transactions can delete the row between insert and update/select.

Also, as explained by the article, this has the table bloating problem



If that's not safe, then Postgres has not implemented transactions correctly. That seems unlikely, so my guess is that what you think I'm suggesting is not what I'm actually suggesting.


there is nothing wrong postgres transactions, they work exactly as intended, but they aren't magic. this is pretty easy to test:

Session 1:

    psql (16.3 (Debian 16.3-1.pgdg120+1))
    Type "help" for help.

    postgres=# CREATE TABLE tags (
      id INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
      name VARCHAR(50) NOT NULL
    );
    CREATE TABLE
    postgres=# ALTER TABLE tags ADD CONSTRAINT tags_name_unique UNIQUE(name);
    ALTER TABLE
    postgres=# INSERT INTO tags (name) VALUES ('A'), ('B') RETURNING *;
     id | name
    ----+------
      1 | A
      2 | B
    (2 rows)

    INSERT 0 2
    postgres=# BEGIN;
    BEGIN
    postgres=*# INSERT INTO tags (name) VALUES ('B') ON CONFLICT DO NOTHING RETURNING *;
     id | name
    ----+------
    (0 rows)

    INSERT 0 0

Session 2:

    psql (16.3 (Debian 16.3-1.pgdg120+1))
    Type "help" for help.

    postgres=# DELETE FROM tags WHERE name = 'B';
    DELETE 1

Session 1 continues:

    postgres=*# SELECT * FROM tags WHERE name = 'B';
     id | name
    ----+------
    (0 rows)

    postgres=*# END;
    COMMIT

(yes, I added `ON CONFLICT DO NOTHING` to avoid aborting the transaction prematurely)


I did some more research. Postgres doesn't handle transactions correctly. Once an error on a constraint occurs, subsequent commands are ignored. You can't handle errors in a transaction. Most RDBMSs don't require a rollback in that situation. It's weird.


You can, you just need to use savepoints.


Yeah the error handling is a side-note here. The main thing to understand is that postgresql transactions have "read committed" isolation level by default, which means that reads will see the writes from any committed other transactions, which means that there can be arbitrary changes to data between operations, e.g. between insert and select, even if no errors or conflicts are involved. This is pretty much what the sample I posted in parent comment demonstrates.


What transaction mode would you need to make this safe? Can you explain further?


I believe PG will abort the transaction on an exception (PK violation) and the subsequent update will not run in the same context that had original violation. So it could result in a data race. I don't know what isolation level would fix that, if any.

My understanding is that in general, if you hit an exception in postgres at all then you can't trust the isolation of the current transaction anymore.

That's what MERGE and speculative insertion (on conflict do update) addresses.


well, yes, you are right that exception does abort the transaction. but even if you use something like 'on conflict do nothing' to avoid the exception, you still can get problems with concurrent writes (see my sibling comment https://news.ycombinator.com/item?id=41169638)

REPEATABLE READ or SERIALIZABLE isolation levels would help with that, as the name suggest repeatable read ensures that the read made by inserts constraint check is repeatable in successive select (or update) statements.

https://www.postgresql.org/docs/current/transaction-iso.html is pretty comprehensive.


Yeah this is correct. I didn't realize it.

Most RDBMSs don't behave this way. They allow you to correct exceptions that occur for anything less than a deadlock without a total rollback, but not Postgres.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: