When using PostgreSQL, certain phenomena can occur due to concurrent transactions. Below are some potential issues that might arise:
-
Dirty Read
A transaction reads data written by a concurrent uncommitted transaction.
Example:
AccountID Balance 1 1000 Two transactions are running concurrently:
- Transaction A: Updates the balance of Account 1 but hasn’t committed yet.
- Transaction B: Reads the balance of Account 1, expecting it to be the committed amount.
Steps Leading to a Dirty Read Anomaly:
-
Transaction A starts and decreases the balance of Account 1 by 200 but does not commit.
UPDATE Accounts SET Balance = 800 WHERE AccountID = ; -- Temporary Balance: 800 (uncommitted)
-
Transaction B reads the balance of Account 1 while Transaction A’s update is still uncommitted.
SELECT Balance FROM Accounts WHERE AccountID = 1; -- Result: 800 (uncommitted value from Transaction A)
-
Transaction A encounters an issue and performs a rollback, undoing its changes.
ROLLBACK; -- Balance reverts back to 1000
-
Now, Transaction B has read a balance of 800, which is incorrect, as it’s based on an uncommitted and rolled-back transaction from Transaction A. The correct balance is actually 1000.
-
Non Repeatable Read
A transaction re-reads data it has previously read and finds that data has been modified by another transaction (that committed since the initial read).
Example:
AccountID Balance 1 1000 Two transactions are running concurrently:
- Transaction A: Reads the balance of Account 1 twice, expecting it to remain the same.
- Transaction B: Updates the balance of Account 1 after Transaction A’s first read but before its second read.
Steps Leading to a Non-Repeatable Read Anomaly:
- Transaction A starts and reads the Balance of Account
SELECT Balance FROM Accounts WHERE AccountID = 1; -- Result: 1000
- Transaction B starts, updates the balance of Account 1, and commits.
UPDATE Accounts SET Balance = 1200 WHERE AccountID = 1; -- New Balance: 1200
- Transaction A reads the Balance of Account 1 again, expecting it to be the same as before.
SELECT Balance FROM Accounts WHERE AccountID = 1; -- Result: 1200
-
Phantom Read
A transaction re-executes a query returning a set of rows that satisfy a search condition and finds that the set of rows satisfying the condition has changed due to another recently-committed transaction.
Example:
BookID Genre 1 Fiction 2 Fiction 3 Mystery 4 Fiction Two transactions run concurrently:
- Transaction A: Counts the number of “Fiction” books available and plans to add a new one if there are fewer than five.
- Transaction B: Inserts a new “Fiction” book into the database.
Steps Leading to a Phantom Read Anomaly
- Transaction A starts and counts the number of books in the “Fiction” genre.
SELECT COUNT(*) FROM Books WHERE Genre = 'Fiction'; -- Result: 3
- Transaction B inserts a new book into the “Fiction” genre and commits.
INSERT INTO Books (BookID, Genre) VALUES (5, 'Fiction');
- Transaction A re-runs its query to count the number of “Fiction” books to check before adding its new book.
SELECT COUNT(*) FROM Books WHERE Genre = 'Fiction'; -- Result: 4 (including new row added by Transaction B)
- Transaction A now sees 4 books in the “Fiction” genre instead of 3, which means it may decide differently based on this phantom row. If it decides to add a book only if there are fewer than five, it will see a different result than expected.
-
Serialization Anomaly
The result of successfully committing a group of transactions is inconsistent with all possible orderings of running those transactions one at a time.