Relational Databases Deep Dive
What is a Relational Database?
Section titled “What is a Relational Database?”A relational database organizes data into tables (relations) with rows (tuples) and columns (attributes). Tables are connected through relationships defined by keys.
ACID Properties: The Foundation
Section titled “ACID Properties: The Foundation”ACID is an acronym for four essential properties that guarantee reliable database transactions:
A: Atomicity
Section titled “A: Atomicity”Atomicity means a transaction is all-or-nothing. Either all operations succeed, or all fail (rollback).
Example: Transferring money between accounts. If deducting from Account A succeeds but adding to Account B fails, both operations are rolled back. You can’t have money disappear!
C: Consistency
Section titled “C: Consistency”Consistency ensures the database always remains in a valid state. All constraints, rules, and relationships are maintained.
Example: If a table has a constraint “age must be > 0”, the database will reject any transaction that tries to insert a negative age, keeping the database consistent.
I: Isolation
Section titled “I: Isolation”Isolation ensures concurrent transactions don’t interfere with each other. Each transaction sees a consistent snapshot of the data.
Example: Two users updating the same account balance simultaneously. Isolation ensures each transaction sees a consistent view and updates don’t interfere.
D: Durability
Section titled “D: Durability”Durability guarantees that once a transaction is committed, it persists even if the system crashes.
Example: After you commit a bank transfer, even if the server crashes immediately after, your transaction is safely stored and will be restored when the system recovers.
Database Normalization
Section titled “Database Normalization”Normalization is the process of organizing data to reduce redundancy and eliminate anomalies. It involves splitting tables and creating relationships.
Why Normalize?
Section titled “Why Normalize?”Normal Forms
Section titled “Normal Forms”Normalization follows normal forms (NF), each eliminating specific types of redundancy:
Normalization Example
Section titled “Normalization Example”Before Normalization (Problems):
| order_id | customer_name | customer_email | product_name | price | quantity |
|---|---|---|---|---|---|
| 1 | Alice | alice@email.com | Laptop | 1000 | 1 |
| 2 | Alice | alice@email.com | Mouse | 20 | 2 |
| 3 | Bob | bob@email.com | Keyboard | 50 | 1 |
Problems:
- ❌ Customer info repeated (redundancy)
- ❌ If Alice changes email, need to update multiple rows
- ❌ If we delete order #2, we lose Alice’s info
After Normalization (3NF):
Customers Table:
| customer_id | name | |
|---|---|---|
| 1 | Alice | alice@email.com |
| 2 | Bob | bob@email.com |
Orders Table:
| order_id | customer_id | product_name | price | quantity |
|---|---|---|---|---|
| 1 | 1 | Laptop | 1000 | 1 |
| 2 | 1 | Mouse | 20 | 2 |
| 3 | 2 | Keyboard | 50 | 1 |
Benefits:
- ✅ Customer info stored once
- ✅ Update email in one place
- ✅ Delete order without losing customer info
Database Indexing
Section titled “Database Indexing”An index is a data structure that speeds up data retrieval. It’s like a book’s index—instead of scanning every page, you look up the index to find the exact location.
How Indexes Work
Section titled “How Indexes Work”Analogy: Finding a word in a dictionary:
- Without index: Read every page (full table scan)
- With index: Use alphabetical index to jump directly (index lookup)
Types of Indexes
Section titled “Types of Indexes”Index Trade-offs
Section titled “Index Trade-offs”| Aspect | Benefit | Cost |
|---|---|---|
| Read Speed | ✅ Much faster queries | ❌ Slower writes (must update index) |
| Storage | - | ❌ Extra storage space |
| Maintenance | - | ❌ Index must be maintained |
LLD ↔ HLD Connection
Section titled “LLD ↔ HLD Connection”How relational database concepts affect your class design:
Entity Classes Map to Tables
Section titled “Entity Classes Map to Tables”1from dataclasses import dataclass2from typing import Optional3from datetime import datetime4
5@dataclass6class User:7 """Maps to 'users' table"""8 id: Optional[int] = None # Primary key9 name: str10 email: str # Has unique index11 created_at: datetime = datetime.now()12
13@dataclass14class Order:15 """Maps to 'orders' table"""16 id: Optional[int] = None # Primary key17 user_id: int # Foreign key -> users.id18 total: float19 created_at: datetime = datetime.now()20
21 # Relationship (not in DB, but in code)22 user: Optional[User] = None1import java.time.LocalDateTime;2
3public class User {4 // Maps to 'users' table5 private Integer id; // Primary key6 private String name;7 private String email; // Has unique index8 private LocalDateTime createdAt = LocalDateTime.now();9
10 // Getters and setters...11}12
13public class Order {14 // Maps to 'orders' table15 private Integer id; // Primary key16 private Integer userId; // Foreign key -> users.id17 private Double total;18 private LocalDateTime createdAt = LocalDateTime.now();19
20 // Relationship (not in DB, but in code)21 private User user;22
23 // Getters and setters...24}Repository Pattern for Data Access
Section titled “Repository Pattern for Data Access”1from abc import ABC, abstractmethod2from typing import List, Optional3
4class UserRepository(ABC):5 @abstractmethod6 def find_by_id(self, user_id: int) -> Optional[User]:7 """Find user by primary key"""8 pass9
10 @abstractmethod11 def find_by_email(self, email: str) -> Optional[User]:12 """Find user by email (uses index)"""13 pass14
15 @abstractmethod16 def save(self, user: User) -> User:17 """Save user (handles insert/update)"""18 pass19
20class SQLUserRepository(UserRepository):21 def __init__(self, db_connection):22 self.db = db_connection23
24 def find_by_email(self, email: str) -> Optional[User]:25 # Uses email index for fast lookup26 query = "SELECT * FROM users WHERE email = %s"27 # Execute query...1import java.util.*;2
3public interface UserRepository {4 Optional<User> findById(Integer userId); // Uses primary key index5 Optional<User> findByEmail(String email); // Uses email index6 User save(User user); // Handles insert/update7}8
9public class SQLUserRepository implements UserRepository {10 private Connection dbConnection;11
12 public Optional<User> findByEmail(String email) {13 // Uses email index for fast lookup14 String query = "SELECT * FROM users WHERE email = ?";15 // Execute query...16 }17}ACID Transactions in Code
Section titled “ACID Transactions in Code”1class AccountService:2 def __init__(self, db_connection):3 self.db = db_connection4
5 def transfer(self, from_account_id: int, to_account_id: int, amount: float):6 # Atomicity: All or nothing7 with self.db.transaction(): # BEGIN TRANSACTION8 try:9 # Deduct from source10 self.db.execute(11 "UPDATE accounts SET balance = balance - %s WHERE id = %s",12 (amount, from_account_id)13 )14
15 # Add to destination16 self.db.execute(17 "UPDATE accounts SET balance = balance + %s WHERE id = %s",18 (amount, to_account_id)19 )20
21 # Consistency: Check constraints22 if amount < 0:23 raise ValueError("Amount must be positive")24
25 # Commit (Durability: persisted)26 self.db.commit() # COMMIT27 except Exception:28 # Rollback on any error (Atomicity)29 self.db.rollback() # ROLLBACK30 raise1import java.sql.*;2
3public class AccountService {4 private Connection dbConnection;5
6 public void transfer(int fromAccountId, int toAccountId, double amount) {7 // Atomicity: All or nothing8 try {9 dbConnection.setAutoCommit(false); // BEGIN TRANSACTION10
11 // Deduct from source12 PreparedStatement stmt1 = dbConnection.prepareStatement(13 "UPDATE accounts SET balance = balance - ? WHERE id = ?"14 );15 stmt1.setDouble(1, amount);16 stmt1.setInt(2, fromAccountId);17 stmt1.executeUpdate();18
19 // Add to destination20 PreparedStatement stmt2 = dbConnection.prepareStatement(21 "UPDATE accounts SET balance = balance + ? WHERE id = ?"22 );23 stmt2.setDouble(1, amount);24 stmt2.setInt(2, toAccountId);25 stmt2.executeUpdate();26
27 // Consistency: Check constraints28 if (amount < 0) {29 throw new IllegalArgumentException("Amount must be positive");30 }31
32 // Commit (Durability: persisted)33 dbConnection.commit(); // COMMIT34 } catch (SQLException e) {35 // Rollback on any error (Atomicity)36 dbConnection.rollback(); // ROLLBACK37 throw new RuntimeException(e);38 }39 }40}Key Takeaways
Section titled “Key Takeaways”What’s Next?
Section titled “What’s Next?”Now that you understand relational databases, let’s explore how isolation levels control what concurrent transactions can see:
Next up: Database Isolation Levels — Learn how read committed, repeatable read, and serializable isolation levels prevent race conditions.