Update where race conditions Postgres (read committed)

前端 未结 1 1843
Happy的楠姐
Happy的楠姐 2021-02-20 02:37

I\'m trying to write a query that updates a row in the \"claim\" table to an active status only if a user doesn\'t have more than two active claims open already. So it\'s very i

1条回答
  •  暖寄归人
    2021-02-20 03:10

    Yes, it's absolutely possible that this could result in more than two active claims, because concurrent transactions can't see each others' changes, so two or more concurrent executions would both see 2 claims and both proceed to update their target claims to make them active.

    See related: Do database transactions prevent race conditions.

    Table lock

    The simplest option is to simply:

    BEGIN;
    LOCK TABLE claim IN EXCLUSIVE MODE;
    UPDATE ...
    COMMIT;
    

    ... but that's a pretty heavy-weight solution.

    Row-level lock on a user object

    Assuming you have a table user for the owner of the claims, you should instead:

    SELECT 1 FROM user WHERE user_id = whatever FOR UPDATE
    

    in the same transaction, before running your UPDATE. That way you'll hold an exclusive row-lock on the user and other SELECT ... FOR UPDATE statements will block on your lock. This lock will also block UPDATEs to and deletes of the user; it will not block plain SELECTs of the user without a FOR UPDATE or FOR SHARE clause.

    See explicit locking in the PostgreSQL manual.

    SERIALIZABLE isolation

    An alternative is to use SERIALIZABLE isolation; PostgreSQL 9.2 and newer have transaction dependency detection that would cause all but one of the conflicting transaction to abort with a serialization failure in the example you give above. So your app has to remember what it tried to do when it starts a transaction and be able to trap errors, detect that they're serialization failures, and re-try it after a serialization failure.

    See transaction isolation in the PostgreSQL manual.

    Advisory locks

    Sometimes there's no good candidate object to take a row lock on, and for some reason or another serializable isolation won't solve the issue or isn't usable for other reasons. That's not the case for you, this is just for general information.

    In such cases you can use PostgreSQL's advisory locks to lock arbitrary numeric values; in this case you'd pg_advisory_xact_lock(active_claim.user_id) for example. The explicit locking chapter has more information.

    0 讨论(0)
提交回复
热议问题