ServerlessBase Blog
  • Introduction to Database Transactions

    A comprehensive guide to understanding database transactions

    Introduction to Database Transactions

    You've probably experienced the panic of a failed database operation that leaves your data in an inconsistent state. Maybe you tried to transfer money between accounts and the system reported success but the money disappeared from one account without appearing in the other. This is where database transactions come in.

    A database transaction is a sequence of operations performed as a single logical unit of work. Transactions ensure that either all operations succeed or none do. If any operation fails, the entire transaction rolls back to its original state. This guarantees data integrity even when things go wrong.

    In this article, you'll learn what transactions are, why they matter, and how to use them effectively in your applications. You'll see concrete examples of common transaction patterns and understand the ACID properties that make transactions reliable.

    The ACID Properties

    Transactions are built on four core properties known as ACID:

    • Atomicity: The transaction is all-or-nothing. Either all operations succeed or none do.
    • Consistency: The database remains in a valid state throughout the transaction.
    • Isolation: Concurrent transactions don't interfere with each other.
    • Durability: Once a transaction commits, the changes are permanent.

    Let's explore each property in detail.

    Atomicity: The All-or-Nothing Principle

    Atomicity means a transaction is indivisible. If you have three operations and the second one fails, the first and third are automatically rolled back. This prevents partial updates that could leave your data in an inconsistent state.

    Consider a scenario where you're updating a user's email address and logging the change in an audit table. If the email update succeeds but the audit log fails, you'd have a user with a new email address but no record of the change. Atomicity prevents this problem.

    BEGIN TRANSACTION;
     
    UPDATE users SET email = 'new@example.com' WHERE id = 123;
    INSERT INTO audit_log (user_id, action, old_email, new_email)
    VALUES (123, 'UPDATE_EMAIL', 'old@example.com', 'new@example.com');
     
    COMMIT;

    If the INSERT statement fails, the UPDATE is automatically rolled back. You don't need to write explicit rollback logic.

    Consistency: Maintaining Database Rules

    Consistency means the database always moves from one valid state to another. Every transaction must obey all defined rules and constraints. This includes foreign key constraints, check constraints, and business rules.

    For example, if you have a constraint that prevents negative balances in an accounts table, a transaction that tries to transfer money from an account with insufficient funds will fail and roll back. The database won't allow the transaction to proceed if it would violate consistency rules.

    Isolation: Preventing Concurrent Conflicts

    Isolation ensures that concurrent transactions don't interfere with each other. If Transaction A is running while Transaction B is also running, A should see the state of the database as it was before B started, or as B committed.

    Without proper isolation, you might encounter several problems:

    • Dirty reads: Reading data that's been modified but not yet committed by another transaction.
    • Non-repeatable reads: Reading the same row twice within a transaction and getting different results.
    • Phantom reads: Reading a set of rows that doesn't exist or has changed due to another transaction.

    Most databases offer different isolation levels to balance performance and consistency. The four standard levels are:

    Isolation LevelDirty ReadsNon-Repeatable ReadsPhantom Reads
    Read UncommittedPossiblePossiblePossible
    Read CommittedNot PossiblePossiblePossible
    Repeatable ReadNot PossibleNot PossiblePossible
    SerializableNot PossibleNot PossibleNot Possible

    Read Committed is the default in many databases. It prevents dirty reads but allows non-repeatable reads and phantom reads. Repeatable Read prevents both dirty and non-repeatable reads but not phantom reads. Serializable is the strongest isolation level and prevents all three problems but can have performance implications.

    Durability: Permanent Changes

    Durability means once a transaction commits, the changes are permanent and will survive system failures. The database writes the changes to durable storage, typically disk, before acknowledging the commit.

    This is why you can safely restart your database server after a transaction commits. The changes are already safely stored on disk.

    Transaction Lifecycle

    Every transaction follows a simple lifecycle:

    1. BEGIN: Start a transaction. This marks the beginning of a transaction block.
    2. Operations: Execute SQL statements within the transaction.
    3. COMMIT: Save all changes permanently.
    4. ROLLBACK: Undo all changes and return to the state before the transaction began.
    BEGIN TRANSACTION;
     
    UPDATE accounts SET balance = balance - 100 WHERE id = 1;
    UPDATE accounts SET balance = balance + 100 WHERE id = 2;
     
    -- If something goes wrong
    ROLLBACK;
     
    -- Or if everything succeeds
    COMMIT;

    Common Transaction Patterns

    1. Transfer Money Between Accounts

    This is the classic transaction example. You need to debit one account and credit another in a single atomic operation.

    BEGIN TRANSACTION;
     
    UPDATE accounts SET balance = balance - 100 WHERE id = 1;
    UPDATE accounts SET balance = balance + 100 WHERE id = 2;
     
    COMMIT;

    If the first UPDATE fails (e.g., insufficient funds), the second UPDATE never executes, and the balance remains unchanged.

    2. Multi-Table Updates

    Sometimes you need to update data across multiple tables in a single transaction.

    BEGIN TRANSACTION;
     
    UPDATE products SET stock = stock - 1 WHERE id = 42;
    INSERT INTO order_items (order_id, product_id, quantity)
    VALUES (1001, 42, 1);
    INSERT INTO order_history (order_id, action, timestamp)
    VALUES (1001, 'ORDER_PLACED', NOW());
     
    COMMIT;

    If the INSERT statements fail, the product stock is not decremented.

    3. Preventing Race Conditions

    When multiple users might update the same data simultaneously, you need to use transactions with proper locking.

    BEGIN TRANSACTION;
     
    SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
     
    -- Check if balance is sufficient
    -- Then update the balance
     
    COMMIT;

    The FOR UPDATE clause locks the row, preventing other transactions from modifying it until this transaction commits or rolls back.

    Best Practices

    Always Use Transactions for Multi-Step Operations

    Never perform multi-step operations without transactions. If you update one table and then another, and the second operation fails, you'll have partial data.

    Choose the Right Isolation Level

    Use the lowest isolation level that meets your requirements. Read Committed is usually sufficient for most applications. Only use Serializable if you absolutely need to prevent phantom reads.

    Keep Transactions Short

    Long-running transactions hold locks and reduce concurrency. Keep your transactions as short as possible, only including the essential operations.

    Handle Errors Gracefully

    Always check for errors after each operation and roll back if something goes wrong.

    BEGIN TRANSACTION;
     
    UPDATE accounts SET balance = balance - 100 WHERE id = 1;
     
    -- Check if the update affected any rows
    IF ROW_COUNT() = 0 THEN
        ROLLBACK;
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Insufficient funds';
    END IF;
     
    UPDATE accounts SET balance = balance + 100 WHERE id = 2;
     
    COMMIT;

    Use Explicit Transactions

    Don't rely on autocommit mode for multi-step operations. Always use explicit BEGIN TRANSACTION and COMMIT or ROLLBACK statements.

    Common Pitfalls

    Forgetting to Commit or Rollback

    If you start a transaction but never commit or roll back, the changes remain pending. This can lead to unexpected behavior and locked rows.

    Long Transactions

    Long transactions hold locks and reduce concurrency. Keep your transactions as short as possible.

    Not Handling Errors

    If an error occurs in the middle of a transaction and you don't roll back, the transaction will remain open and hold locks.

    Using the Wrong Isolation Level

    Using too low an isolation level can lead to data consistency issues. Using too high an isolation level can reduce performance.

    Conclusion

    Database transactions are essential for maintaining data integrity in your applications. They ensure that complex operations succeed or fail as a whole, preventing partial updates that could corrupt your data.

    Remember the four ACID properties: atomicity, consistency, isolation, and durability. Use transactions for all multi-step operations, choose the right isolation level, keep transactions short, and handle errors gracefully.

    Platforms like ServerlessBase simplify transaction management by handling connection pooling and providing managed database services. You can focus on writing correct transaction logic without worrying about low-level database details.

    The next time you need to update multiple tables or perform complex operations, remember to wrap them in a transaction. Your data will thank you.

    Leave comment