Assume we have a table that describes the contents of a user's basket of fruit. This is essentially just a mapping of user -> list of fruit.
Periodically, we query a remote data source and get a new list for a particular user. Then, we try to wholly replace all of that user's current rows with a new set of rows. For example:
Before:
user fruit freshness
-------------------------------
...
47 'apple' 0.1
47 'pear' 9.5
47 'pear' 2.8
...
After:
user fruit freshness
-------------------------------
...
47 'apple' 93.9034
47 'banana' 0
...
Given a new set of rows, is there a way to do the replacement that is atomic in postgres?
The following does NOT work:
BEGIN
DELETE FROM basket WHERE user=47
INSERT INTO basket ...
COMMIT
If we have multiple transactions going on simultaneously, postgres will happily order commands like this (with no locking issues):
BEGIN
BEGIN
DELETE
DELETE
INSERT
INSERT <---- data inserted twice here
COMMIT
COMMIT
This will result in twice as many rows as there should be.
A lot of answers to similar questions claim that the first DELETE will lock the rows in question and the second transaction will have to wait, but this isn't true (for whatever reason). Both transactions happily step on each other's toes here.
Notes:
- Preferably, a solution would make it so that it's not possible for a reader to see the intermediate state (no rows), but at this point I could live without it.
- Is my DB just structured wrong? Really I just want to be able to store a map of
user->fruit[]. I could just stuff this all in a single jsonb column, but then I can't run joins on the rows. And joining is nice :(