Database Transactions: ACID, Isolation Levels & Deadlocks
Database transactions are fundamental to ensuring data consistency and integrity in modern applications. They enable safe, reliable operations even when multiple users access the system simultaneously.
In the world of databases, we face several critical challenges: How do we ensure money isn’t lost during transfers? How do we prevent two users from modifying the same data at the same time and overwriting each other’s changes? How do we guarantee that system crashes don’t leave us with inconsistent data?
The answer lies in transactions — the core mechanism for safe database operations. Transactions follow the ACID properties (Atomicity, Consistency, Isolation, Durability), which ensure that data operations either complete fully or don’t happen at all.
When multiple users access the database concurrently, new challenges emerge: Isolation levels define how much transactions can affect each other, ranging from simple read issues to phantom rows. Deadlocks can occur when transactions wait on each other and block one another. Optimistic concurrency offers a modern alternative to traditional pessimistic locking by detecting and resolving conflicts at commit time.
These concepts aren’t merely theoretical — they’re critical for banking systems, e-commerce platforms, social media applications, and any software that reliably handles persistent data.
What Are Transactions?
Definition and Fundamentals
A transaction is a logical unit of work consisting of one or more database operations. Transactions must be treated as atomic units — either all operations succeed, or none of them do.
Transaction Properties
-- Transaction as a logical unit
BEGIN TRANSACTION;
-- Operation 1: Debit account
UPDATE Konten SET saldo = saldo - 100 WHERE konto_id = 1;
-- Operation 2: Credit account
UPDATE Konten SET saldo = saldo + 100 WHERE konto_id = 2;
-- Either both operations succeed or neither does
COMMIT;
-- or on error: ROLLBACK;
ACID Properties
The ACID properties form the foundation of reliable transaction systems. They guarantee that database operations remain consistent and reliable even when facing errors, system crashes, or concurrent access from multiple users. Each property addresses specific challenges: Atomicity prevents partial operations, Consistency upholds business rules, Isolation protects transactions from interfering with each other, and Durability ensures permanent storage.
Atomicity
Atomicity ensures that a transaction either executes completely or not at all. This is critical for multi-step operations like bank transfers, where money must be debited from one account and credited to another simultaneously. Without atomicity, system crashes during execution could leave you with inconsistent data.
-- Example of atomicity
CREATE TABLE Transaktionen (
trans_id INT PRIMARY KEY AUTO_INCREMENT,
von_konto INT,
zu_konto INT,
betrag DECIMAL(10,2),
status VARCHAR(20),
zeitpunkt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Atomic transfer
DELIMITER //
CREATE PROCEDURE ueberweisung(
IN von_konto_id INT,
IN zu_konto_id INT,
IN betrag DECIMAL(10,2)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
-- Check if balance is sufficient
DECLARE aktuelles_saldo DECIMAL(10,2);
SELECT saldo INTO aktuelles_saldo FROM Konten WHERE konto_id = von_konto_id;
IF aktuelles_saldo < betrag THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Unzureichendes Saldo';
END IF;
-- Debit money
UPDATE Konten SET saldo = saldo - betrag WHERE konto_id = von_konto_id;
-- Credit money
UPDATE Konten SET saldo = saldo + betrag WHERE konto_id = zu_konto_id;
-- Log transaction
INSERT INTO Transaktionen (von_konto, zu_konto, betrag, status)
VALUES (von_konto_id, zu_konto_id, betrag, 'ERFOLGREICH');
COMMIT;
END //
DELIMITER ;
-- Call
CALL ueberweisung(1, 2, 100.00);
What this example demonstrates: The stored procedure achieves perfect atomicity through the DECLARE EXIT HANDLER FOR SQLEXCEPTION, which automatically triggers a ROLLBACK on any error. The entire transfer — balance check, debit, credit, and logging — is treated as an indivisible unit.
Key points in this example:
- Error Handling: The handler catches all SQL errors and ensures automatic rollback
- Balance Validation: Before execution, the system checks that sufficient funds exist
- Completeness: All four steps must succeed, or none will execute
- Logging: The transaction is only recorded on success, ensuring audit trail consistency
Consistency
Consistency ensures that the database remains in a valid state after a transaction completes. This property upholds business rules and data integrity by guaranteeing that all defined constraints, triggers, and relationships are honored. For a bank transfer, this means the total balance across all accounts stays constant and no account can go negative.
-- Example of consistency rules
CREATE TABLE Konten (
konto_id INT PRIMARY KEY,
inhaber VARCHAR(100),
saldo DECIMAL(10,2) NOT NULL,
CHECK (saldo >= 0) -- Balance cannot be negative
);
CREATE TABLE Ueberweisungsregeln (
regel_id INT PRIMARY KEY,
max_betrag_pro_tag DECIMAL(10,2),
max_anzahl_pro_tag INT
);
-- Consistency-preserving transfer
DELIMITER //
CREATE PROCEDURE konsistente_ueberweisung(
IN von_konto_id INT,
IN zu_konto_id INT,
IN betrag DECIMAL(10,2)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
-- Business rule: check maximum amount
DECLARE max_betrag DECIMAL(10,2);
SELECT max_betrag_pro_tag INTO max_betrag
FROM Ueberweisungsregeln WHERE regel_id = 1;
IF betrag > max_betrag THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Betrag exceeds maximum';
END IF;
-- Balance check (enforced by CHECK constraint)
DECLARE aktuelles_saldo DECIMAL(10,2);
SELECT saldo INTO aktuelles_saldo FROM Konten WHERE konto_id = von_konto_id;
IF aktuelles_saldo < betrag THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Insufficient funds';
END IF;
-- Execute operations
UPDATE Konten SET saldo = saldo - betrag WHERE konto_id = von_konto_id;
UPDATE Konten SET saldo = saldo + betrag WHERE konto_id = zu_konto_id;
COMMIT;
END //
DELIMITER ;
What this example demonstrates: Consistency is enforced through multiple mechanisms: the CHECK (saldo >= 0) constraint prevents negative balances, the stored procedure validates business rules before execution, and violations trigger automatic rollback.
Key points in this example:
- Database Constraints: The CHECK constraint enforces business rules at the database layer
- Business Logic Validation: The stored procedure implements additional business rules
- Automatic Rollback: Constraint violations cause the transaction to abort automatically
- Data Integrity: Multiple layers of consistency protection (constraint + application logic)
Isolation
Isolation ensures that concurrent transactions don’t interfere with each other. It prevents common concurrency issues like Dirty Reads (reading uncommitted data), Non-Repeatable Reads (getting different results when reading the same data twice), and Phantom Reads (new rows appearing between reads). Isolation is controlled through different isolation levels, which balance consistency against performance.
-- Isolation example
-- Session 1:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
-- Session 2 (parallel):
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
-- Session 1 reads data
SELECT * FROM accounts WHERE account_id = 1;
-- Session 2 modifies data
UPDATE accounts SET balance = 1500 WHERE account_id = 1;
COMMIT;
-- Session 1 reads again (behavior depends on isolation level)
SELECT * FROM accounts WHERE account_id = 1;
Durability
Durability ensures that changes made by a committed transaction are permanently stored.
-- Durability example
-- After COMMIT, changes are permanent
START TRANSACTION;
UPDATE accounts SET balance = 2000 WHERE account_id = 1;
COMMIT; -- Changes are now permanent
-- Changes survive even system crashes
-- (through Write-Ahead Logging and other mechanisms)
Isolation Levels
Isolation levels define how much concurrent transactions are allowed to affect each other. They represent a crucial tradeoff between data consistency and system performance: higher isolation provides stronger guarantees but introduces more locking overhead and potentially slower operations. Choosing the right isolation level depends on your application’s requirements and how critical data consistency is.
READ UNCOMMITTED
The lowest isolation level—allows Dirty Reads. This level is rarely used because it can lead to inconsistent data, but it maximizes performance by using minimal locking.
-- READ UNCOMMITTED example
-- Session 1:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
UPDATE accounts SET balance = 500 WHERE account_id = 1;
-- Not yet committed!
-- Session 2:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT * FROM accounts WHERE account_id = 1;
-- Reads the uncommitted value (500) - Dirty Read!
-- Session 1:
ROLLBACK; -- Change is rolled back
-- Session 2 has read invalid data
What this example demonstrates: READ UNCOMMITTED allows reading data that hasn’t been committed yet. Session 2 reads a value (500) that Session 1 later rolls back, resulting in inconsistent data.
Key points in this example:
- Dirty Read Problem: Session 2 reads unconfirmed data that later becomes invalid
- Performance Advantage: No locks required, maximum read speed
- Data Inconsistency: Risk of reading stale or contradictory values
- Use Case: Only suitable for systems where absolute data consistency isn’t critical
READ COMMITTED
Prevents Dirty Reads but allows Non-Repeatable Reads.
-- READ COMMITTED example
-- Session 1:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT balance FROM accounts WHERE account_id = 1; -- Reads 1000
-- Session 2:
START TRANSACTION;
UPDATE accounts SET balance = 1500 WHERE account_id = 1;
COMMIT;
-- Session 1 reads again:
SELECT balance FROM accounts WHERE account_id = 1; -- Now reads 1500
-- Non-Repeatable Read!
REPEATABLE READ
Prevents Dirty Reads and Non-Repeatable Reads but allows Phantom Reads.
-- REPEATABLE READ example
-- Session 1:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT * FROM accounts WHERE holder LIKE 'A%'; -- Reads 3 accounts
-- Session 2:
START TRANSACTION;
INSERT INTO accounts VALUES (4, 'Anna Schmidt', 2000);
COMMIT;
-- Session 1 reads again:
SELECT * FROM accounts WHERE holder LIKE 'A%'; -- Still 3 accounts
-- New row isn't visible (no Phantom Read in MySQL)
SERIALIZABLE
The highest isolation level—prevents all anomalies.
-- SERIALIZABLE example
-- Session 1:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT AVG(balance) FROM accounts; -- Calculates average
-- Session 2:
START TRANSACTION;
INSERT INTO accounts VALUES (5, 'Bernd Mueller', 3000);
-- Waits until Session 1 finishes!
-- Session 1:
COMMIT;
-- Session 2 can now proceed
COMMIT;
Concurrency Control
Pessimistic Concurrency Control
Locks resources proactively to prevent conflicts.
-- Pessimistic Locking example
-- Explicit Locks
START TRANSACTION;
-- Lock the row
SELECT * FROM accounts WHERE account_id = 1 FOR UPDATE;
-- Other transactions must wait
-- Session 2:
SELECT * FROM accounts WHERE account_id = 1 FOR UPDATE;
-- Waits until Session 1 commits/rolls back
-- Perform operations
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
COMMIT; -- Lock is released
Optimistic Concurrency Control
Assumes conflicts are rare and resolves them only when necessary. This approach works particularly well in systems with many reads but few writes. Instead of proactively locking resources (pessimistic locking), conflicts are detected during validation and the transaction is retried if needed. This reduces locking overhead and improves scalability.
-- Optimistic Concurrency with Version Column
CREATE TABLE products (
product_id INT PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10,2),
stock INT,
version INT DEFAULT 0
);
-- Update with version check
DELIMITER //
CREATE PROCEDURE update_product_optimistic(
IN product_id INT,
IN new_price DECIMAL(10,2),
IN expected_version INT
)
BEGIN
DECLARE affected_rows INT;
START TRANSACTION;
UPDATE products
SET price = new_price, version = version + 1
WHERE product_id = product_id AND version = expected_version;
SET affected_rows = ROW_COUNT();
IF affected_rows = 0 THEN
ROLLBACK;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Concurrency conflict - product was modified';
ELSE
COMMIT;
END IF;
END //
DELIMITER ;
-- Application usage
-- Read first
SELECT product_id, name, price, version FROM products WHERE product_id = 1;
-- Update with expected version
CALL update_product_optimistic(1, 29.99, 5);
What this example demonstrates: Optimistic Concurrency Control uses a version column to detect conflicts. The UPDATE statement checks whether the expected version is still current and increments the version on successful update.
Key points in this example:
- Version Column: Each change increments the version, enabling a change history
- Conflict Detection: The WHERE clause checks the version before updating
- No Locking: No explicit locks required, better performance for read-heavy workloads
- Retry Logic: On conflict, the application must retry the transaction
Deadlocks
Detecting and Preventing Deadlocks
Deadlocks occur when transactions wait for each other and mutually block access to resources. This is a classic concurrency problem: two or more transactions each hold a lock and wait for a lock held by another transaction. Deadlocks bring the system to a standstill and must be automatically detected and resolved, typically by aborting one of the involved transactions.
-- Deadlock example
-- Session 1:
START TRANSACTION;
UPDATE Konten SET saldo = saldo - 100 WHERE konto_id = 1;
-- Waits for konto_id = 2
UPDATE Konten SET saldo = saldo + 100 WHERE konto_id = 2;
-- Session 2 (running in parallel):
START TRANSACTION;
UPDATE Konten SET saldo = saldo - 50 WHERE konto_id = 2;
-- Waits for konto_id = 1
UPDATE Konten SET saldo = saldo + 50 WHERE konto_id = 1;
-- DEADLOCK! Both are now waiting for each other
What this example illustrates: A textbook deadlock scenario. Session 1 locks account 1 and tries to access account 2, while Session 2 locks account 2 and waits for account 1. Both transactions block each other indefinitely.
Key points in this example:
- Circular Wait: Both transactions wait on resources locked by the other
- Resource Holding: Each transaction already holds a lock and waits for another
- Deadlock Detection: The database must recognize the deadlock and abort one transaction
- Prevention Strategy: A consistent lock ordering could avoid this deadlock entirely
Deadlock Prevention Strategies
-- 1. Consistent lock ordering
DELIMITER //
CREATE PROCEDURE sichere_ueberweisung(
IN von_konto_id INT,
IN zu_konto_id INT,
IN betrag DECIMAL(10,2)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
-- Always acquire locks in the same order
IF von_konto_id < zu_konto_id THEN
SET @lock1 = von_konto_id;
SET @lock2 = zu_konto_id;
ELSE
SET @lock1 = zu_konto_id;
SET @lock2 = von_konto_id;
END IF;
START TRANSACTION;
-- Locks in consistent order
SELECT * FROM Konten WHERE konto_id = @lock1 FOR UPDATE;
SELECT * FROM Konten WHERE konto_id = @lock2 FOR UPDATE;
-- Perform operations
IF von_konto_id < zu_konto_id THEN
UPDATE Konten SET saldo = saldo - betrag WHERE konto_id = von_konto_id;
UPDATE Konten SET saldo = saldo + betrag WHERE konto_id = zu_konto_id;
ELSE
UPDATE Konten SET saldo = saldo + betrag WHERE konto_id = zu_konto_id;
UPDATE Konten SET saldo = saldo - betrag WHERE konto_id = von_konto_id;
END IF;
COMMIT;
END //
DELIMITER ;
-- 2. Timeout-based retry logic
DELIMITER //
CREATE PROCEDURE ueberweisung_mit_retry(
IN von_konto_id INT,
IN zu_konto_id INT,
IN betrag DECIMAL(10,2)
)
BEGIN
DECLARE retry_count INT DEFAULT 0;
DECLARE max_retries INT DEFAULT 3;
DECLARE deadlock_detected BOOLEAN DEFAULT FALSE;
retry_loop: WHILE retry_count < max_retries DO
BEGIN
DECLARE EXIT HANDLER FOR 1213 -- Deadlock error code
BEGIN
SET deadlock_detected = TRUE;
SET retry_count = retry_count + 1;
IF retry_count < max_retries THEN
-- Brief wait before retry
DO SLEEP(0.1 * retry_count);
END IF;
END;
-- Execute transaction
CALL sichere_ueberweisung(von_konto_id, zu_konto_id, betrag);
-- Success - exit loop
LEAVE retry_loop;
END;
IF deadlock_detected AND retry_count >= max_retries THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Max retries exceeded';
END IF;
SET deadlock_detected = FALSE;
END WHILE;
END //
DELIMITER ;
Monitoring Deadlocks
-- Retrieve deadlock information (MySQL)
SHOW ENGINE INNODB STATUS;
-- Monitor blocking transactions
SELECT
r.trx_id waiting_trx_id,
r.trx_mysql_thread_id waiting_thread,
r.trx_query waiting_query,
b.trx_id blocking_trx_id,
b.trx_mysql_thread_id blocking_thread,
b.trx_query blocking_query
FROM information_schema.innodb_lock_waits w
INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;
-- Display lock status
SELECT
object_name,
lock_type,
lock_mode,
lock_status,
engine_transaction_id
FROM performance_schema.data_locks
WHERE object_name = 'Konten';
Transaction Management Across Different Databases
MySQL/MariaDB
-- MySQL-specific features
-- Control autocommit
SET autocommit = 0; -- Manual transaction control
SET autocommit = 1; -- Automatic commit (default)
-- Savepoints for partial rollbacks
START TRANSACTION;
UPDATE Konten SET saldo = saldo - 100 WHERE konto_id = 1;
SAVEPOINT sp1;
UPDATE Konten SET saldo = saldo - 50 WHERE konto_id = 2;
SAVEPOINT sp2;
-- Rollback to savepoint
ROLLBACK TO sp1;
COMMIT; -- Only first change persists
-- XA transactions for distributed systems
XA START 'xid1';
UPDATE Konten SET saldo = saldo - 100 WHERE konto_id = 1;
XA END 'xid1';
XA PREPARE 'xid1';
XA COMMIT 'xid1';
PostgreSQL
-- PostgreSQL-specific features
-- Transaction isolation levels
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Advisory locks (application-level locking)
SELECT pg_advisory_lock(12345); -- Acquire lock
SELECT pg_advisory_unlock(12345); -- Release lock
-- Transaction snapshots
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM Konten WHERE konto_id = 1;
-- In another session:
UPDATE Konten SET saldo = 2000 WHERE konto_id = 1;
COMMIT;
-- Original session still sees the old value
SELECT * FROM Konten WHERE konto_id = 1;
Oracle
-- Oracle-specific features
-- Read-only transactions
SET TRANSACTION READ ONLY;
SELECT * FROM Konten; -- Guarantees consistent view
-- Autonomous transactions
DELIMITER //
CREATE PROCEDURE log_transaktion(
IN transaktion_id INT,
IN beschreibung VARCHAR(200)
)
AS
BEGIN
-- Autonomous transaction
PRAGMA AUTONOMOUS_TRANSACTION;
INSERT INTO Transaktionslog (trans_id, beschreibung, zeitpunkt)
VALUES (transaktion_id, beschreibung, SYSTIMESTAMP);
COMMIT; -- Commits only this autonomous transaction
END;
//
-- Savepoints
SAVEPOINT sp1;
-- Operations
ROLLBACK TO sp1;
Best Practices for Transaction Management
1. Keep transactions short
-- Bad: Long-running transaction
START TRANSACTION;
SELECT * FROM grosse_tabelle; -- Expensive query
-- ... many other operations ...
UPDATE kleine_tabelle SET wert = 1;
COMMIT;
-- Good: Short, focused transaction
SELECT * FROM grosse_tabelle; -- Outside the transaction
START TRANSACTION;
UPDATE kleine_tabelle SET wert = 1;
COMMIT;
2. Choose the right isolation level
-- For most applications
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- For analytical queries
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- For critical financial operations
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
3. Implementing error handling
-- Robust error handling
DELIMITER //
CREATE PROCEDURE robuste_transaktion()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1 @sqlstate = RETURNED_SQLSTATE,
@errno = MYSQL_ERRNO,
@text = MESSAGE_TEXT;
ROLLBACK;
-- Logging
INSERT INTO error_log (error_time, error_code, error_message)
VALUES (NOW(), @errno, @text);
-- Re-raise error
RESIGNAL;
END;
START TRANSACTION;
-- Transaction logic
INSERT INTO tabelle1 (wert) VALUES (1);
UPDATE tabelle2 SET wert = 2 WHERE id = 1;
COMMIT;
END //
DELIMITER ;
4. Using connection pooling
// Java example with connection pool
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class TransactionService {
private DataSource dataSource;
public void executeInTransaction(TransactionCallback callback) {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false);
callback.execute(conn);
conn.commit();
} catch (SQLException e) {
if (conn != null) {
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
throw new RuntimeException("Transaction failed", e);
} finally {
if (conn != null) {
try {
conn.setAutoCommit(true);
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
@FunctionalInterface
public interface TransactionCallback {
void execute(Connection conn) throws SQLException;
}
}
Key concepts for assessments
Core ACID properties
| Property | Description | Implementation |
|---|---|---|
| Atomicity | All or nothing | Rollback, Write-Ahead Logging |
| Consistency | Consistent state | Constraints, Triggers |
| Isolation | No interference | Locks, Isolation Levels |
| Durability | Permanent storage | Redo Logs, Checkpoints |
Isolation levels compared
| Level | Dirty Reads | Non-Repeatable Reads | Phantom Reads | Performance |
|---|---|---|---|---|
| READ UNCOMMITTED | ✅ | ✅ | ✅ | Highest |
| READ COMMITTED | ❌ | ✅ | ✅ | High |
| REPEATABLE READ | ❌ | ❌ | ✅ (MySQL: ❌) | Medium |
| SERIALIZABLE | ❌ | ❌ | ❌ | Lowest |
Common exam tasks
- Explain ACID properties
- Compare isolation levels
- Implement deadlock avoidance
- Choose appropriate transaction strategies
- Analyze concurrency issues
Summary
Transaction management is fundamental to building reliable database applications:
- ACID properties guarantee data integrity
- Isolation levels control concurrency behavior
- Deadlock avoidance ensures system stability
- Optimistic vs pessimistic concurrency control
- Best practices optimize performance and reliability
Effective transaction design requires understanding both your application’s needs and the underlying database mechanisms.
Recommended reading: Databases
Keine Bücher für Kategorie "datenbanken" gefunden.

