PostgreSQL Transactions

Ensure data consistency with ACID properties

🔐 What are Transactions?

Transactions are sequences of database operations that execute as a single unit. They ensure data integrity by guaranteeing that either all operations succeed together or none take effect, maintaining database consistency.


-- Basic transaction syntax
BEGIN;
    -- SQL statements here
COMMIT;  -- or ROLLBACK;
                                    

ACID Properties

⚛️

Atomicity

All or nothing execution

-- Complete or rollback

Consistency

Valid state transitions only

-- Maintain constraints
🔒

Isolation

Concurrent transactions isolated

-- No interference
💾

Durability

Changes persist after commit

-- Permanent storage

🔹 Basic Transaction Control

Transactions start with BEGIN and end with either COMMIT to save changes or ROLLBACK to undo them. This control ensures that related operations either all succeed or all fail together.

-- Create sample tables
CREATE TABLE accounts (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    balance DECIMAL(10,2)
);

INSERT INTO accounts (name, balance) VALUES
    ('Alice', 1000.00),
    ('Bob', 500.00);

-- Successful transaction
BEGIN;
    UPDATE accounts SET balance = balance - 100 WHERE name = 'Alice';
    UPDATE accounts SET balance = balance + 100 WHERE name = 'Bob';
COMMIT;

-- Check results
SELECT * FROM accounts;

Result:

id | name  | balance
---+-------+---------
 1 | Alice | 900.00
 2 | Bob   | 600.00

🔹 Rolling Back Transactions

ROLLBACK undoes all changes made within a transaction, returning the database to its state before BEGIN. This is crucial for handling errors and maintaining data integrity when operations fail.

-- Transaction with rollback
BEGIN;
    UPDATE accounts SET balance = balance - 200 WHERE name = 'Alice';
    UPDATE accounts SET balance = balance + 200 WHERE name = 'Bob';
    -- Oops, made a mistake!
ROLLBACK;

-- Check results (no changes)
SELECT * FROM accounts;

Result:

id | name  | balance
---+-------+---------
 1 | Alice | 900.00
 2 | Bob   | 600.00

-- Balances unchanged because of ROLLBACK

🔹 Savepoints

Savepoints allow partial rollbacks within a transaction. You can set multiple savepoints and roll back to any of them without losing all transaction work, providing fine-grained control over changes.

-- Transaction with savepoints
BEGIN;
    UPDATE accounts SET balance = balance - 50 WHERE name = 'Alice';
    SAVEPOINT sp1;
    
    UPDATE accounts SET balance = balance + 50 WHERE name = 'Bob';
    SAVEPOINT sp2;
    
    UPDATE accounts SET balance = balance - 100 WHERE name = 'Alice';
    -- Rollback to sp2 (undo last update only)
    ROLLBACK TO SAVEPOINT sp2;
    
COMMIT;

-- Check results
SELECT * FROM accounts;

Result:

id | name  | balance
---+-------+---------
 1 | Alice | 850.00  -- First update kept
 2 | Bob   | 650.00  -- Second update kept

-- Third update was rolled back

🔹 Transaction Isolation Levels

Isolation levels control how transactions interact with each other. PostgreSQL supports four levels that balance between data consistency and concurrent access performance, preventing various concurrency issues.

-- Set isolation level
BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;
    -- Your queries here
COMMIT;

-- Available isolation levels:
-- READ UNCOMMITTED (treated as READ COMMITTED in PostgreSQL)
-- READ COMMITTED (default)
-- REPEATABLE READ
-- SERIALIZABLE

-- Example: Repeatable Read
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
    SELECT * FROM accounts WHERE name = 'Alice';
    -- Other transactions can't modify this row until we commit
    UPDATE accounts SET balance = balance + 100 WHERE name = 'Alice';
COMMIT;

Isolation Levels:

READ COMMITTED    - See committed changes from other transactions
REPEATABLE READ   - Consistent snapshot throughout transaction
SERIALIZABLE      - Full isolation, as if transactions run serially

🔹 Handling Deadlocks

Deadlocks occur when transactions wait for each other's locks. PostgreSQL automatically detects deadlocks and aborts one transaction. Your application should catch these errors and retry the transaction.

-- Deadlock scenario (for illustration)
-- Transaction 1:
BEGIN;
    UPDATE accounts SET balance = balance - 100 WHERE id = 1;
    -- Wait...
    UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;

-- Transaction 2 (running simultaneously):
BEGIN;
    UPDATE accounts SET balance = balance - 50 WHERE id = 2;
    -- Wait...
    UPDATE accounts SET balance = balance + 50 WHERE id = 1;
COMMIT;

-- PostgreSQL will detect deadlock and abort one transaction
-- Error: deadlock detected

Avoiding Deadlocks:

1. Access tables in same order
2. Keep transactions short
3. Use appropriate isolation levels
4. Implement retry logic in application

🔹 Transaction Best Practices

Following transaction best practices ensures optimal database performance and data integrity. Keep transactions focused, handle errors properly, and choose appropriate isolation levels for your use case.

-- Good transaction pattern
BEGIN;
    -- Check if operation is valid
    SELECT balance INTO current_balance 
    FROM accounts 
    WHERE name = 'Alice';
    
    IF current_balance >= 100 THEN
        UPDATE accounts 
        SET balance = balance - 100 
        WHERE name = 'Alice';
        
        UPDATE accounts 
        SET balance = balance + 100 
        WHERE name = 'Bob';
        
        COMMIT;
    ELSE
        ROLLBACK;
        RAISE EXCEPTION 'Insufficient funds';
    END IF;
END;

🔹 Transaction Guidelines

Effective transaction management is crucial for database reliability and performance. These guidelines help you design transactions that maintain data integrity while maximizing concurrent access and system throughput.

  • Keep Short: Minimize transaction duration to reduce locks
  • Explicit Control: Always use BEGIN/COMMIT explicitly
  • Error Handling: Always handle potential errors with ROLLBACK
  • Consistent Order: Access tables in same order to avoid deadlocks
  • Right Isolation: Use lowest isolation level that meets requirements
  • Avoid User Input: Don't wait for user input during transactions

🧠 Test Your Knowledge

Which command saves all changes made in a transaction?