SQL vs NoSQL: Choosing the Right Database
A comprehensive comparison of relational and non-relational databases, their trade-offs, and decision frameworks for system design.
Choosing between SQL and NoSQL is one of the most fundamental architectural decisions. This guide helps you make an informed choice based on your specific requirements.
Quick Comparison
| Aspect | SQL (Relational) | NoSQL (Non-Relational) |
|---|---|---|
| Data Model | Tables, rows, columns | Documents, key-value, wide-column, graph |
| Schema | Fixed, enforced | Flexible, schema-on-read |
| Query Language | SQL (standardized) | Varies by database |
| Transactions | ACID (strong) | BASE (eventual) |
| Scaling | Vertical (primarily) | Horizontal (native) |
| Joins | Native support | Limited/denormalized |
| Consistency | Strong | Eventual (tunable) |
SQL Databases
When to Choose SQL
- Complex relationships: Foreign keys, joins, many-to-many
- ACID transactions required: Financial, inventory, booking systems
- Structured, stable schema: Well-defined entities that don’t change often
- Ad-hoc queries: Need flexible querying without knowing access patterns upfront
- Mature ecosystem: Tooling, ORMs, migration tools, expertise
Popular SQL Databases
| Database | Best For | Notable Features |
|---|---|---|
| PostgreSQL | General purpose, complex queries | JSONB, extensions, window functions |
| MySQL | Web applications, read-heavy | Simple, fast, wide adoption |
| SQL Server | Enterprise, .NET ecosystem | T-SQL, integration services |
| CockroachDB | Distributed SQL | Horizontal scaling, strong consistency |
| TiDB | HTAP (hybrid transactional/analytical) | MySQL compatible, distributed |
Schema Design Example
-- Normalized schema for e-commerce
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
status VARCHAR(50) NOT NULL DEFAULT 'pending',
total_cents INTEGER NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE order_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_id UUID REFERENCES orders(id),
product_id UUID NOT NULL,
quantity INTEGER NOT NULL,
price_cents INTEGER NOT NULL
);
-- Indexes for common queries
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status_created ON orders(status, created_at DESC);
NoSQL Databases
Types of NoSQL
Document Databases (MongoDB, Couchbase)
- Store JSON-like documents
- Flexible schema, nested data
- Good for: Content management, catalogs, user profiles
Key-Value Stores (Redis, DynamoDB, Riak)
- Simple key → value mapping
- Extremely fast reads/writes
- Good for: Caching, sessions, shopping carts
Wide-Column Stores (Cassandra, HBase, ScyllaDB)
- Column families, sparse columns
- Time-series, high write throughput
- Good for: Logging, metrics, sensor data
Graph Databases (Neo4j, Amazon Neptune, ArangoDB)
- Nodes and relationships
- Traversals, recommendations
- Good for: Social networks, fraud detection, knowledge graphs
When to Choose NoSQL
- Massive scale: Petabytes, millions of writes/second
- Flexible schema: Rapidly changing data structures
- Specific access patterns: Known query patterns, no ad-hoc queries
- High availability: AP systems for global applications
- Specialized workloads: Time-series, graphs, key-value
NoSQL Data Modeling
// MongoDB: Embedded vs Referenced
// Embedded (one-to-few, read together)
{
_id: ObjectId("..."),
title: "System Design Notes",
author: { name: "John", email: "john@example.com" },
sections: [
{ title: "CAP Theorem", content: "...", order: 1 },
{ title: "Consensus", content: "...", order: 2 }
],
tags: ["distributed-systems", "architecture"],
createdAt: ISODate("2024-01-15")
}
// Referenced (one-to-many, independent lifecycle)
{
_id: ObjectId("..."),
title: "System Design Notes",
authorId: ObjectId("..."), // Reference to users collection
sectionIds: [ObjectId("..."), ObjectId("...")], // Array of refs
tags: ["distributed-systems", "architecture"]
}
Decision Framework
START: What's your data like?
├── Highly relational, complex joins?
│ └── YES → SQL (PostgreSQL)
├── Need ACID transactions?
│ └── YES → SQL (PostgreSQL, CockroachDB)
├── Massive scale, simple access patterns?
│ └── YES → NoSQL (Cassandra, DynamoDB)
├── Flexible schema, rapid iteration?
│ └── YES → NoSQL (MongoDB)
├── Graph relationships, traversals?
│ └── YES → Graph DB (Neo4j)
├── Key-value, caching, sessions?
│ └── YES → Redis, DynamoDB
└── Time-series, metrics, logs?
└── YES → Wide-column (TimescaleDB, InfluxDB)
Hybrid Approaches
Many modern systems use polyglot persistence:
┌─────────────────────────────────────────────────┐
│ Application │
└──────────┬──────────────┬───────────┬───────────┘
│ │ │
┌──────▼──────┐ ┌─────▼────┐ ┌────▼────┐
│ PostgreSQL │ │ Redis │ │Elastic │
│ (Orders, │ │ (Cache, │ │Search │
│ Users, │ │ Sessions)│ │(Logs, │
│ Payments) │ │ │ │ Docs) │
└─────────────┘ └──────────┘ └─────────┘
NewSQL: Best of Both Worlds
NewSQL databases provide horizontal scaling with ACID:
- CockroachDB: PostgreSQL-compatible, geo-distributed
- TiDB: MySQL-compatible, HTAP
- Google Spanner: Global consistency with TrueTime
- YugabyteDB: PostgreSQL-compatible, multi-cloud
-- CockroachDB: Globally distributed, strong consistency
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email STRING UNIQUE NOT NULL,
region STRING NOT NULL
)
WITH (replication_zone = 'global');
Checklist for Database Selection
- What are the access patterns? (Known vs ad-hoc)
- What are the consistency requirements? (Strong vs eventual)
- What is the scale? (Current and projected)
- What is the team expertise? (SQL vs NoSQL experience)
- What are the operational constraints? (Managed vs self-hosted)
- What are the compliance requirements? (ACID, audit trails)
- What is the data lifecycle? (Retention, archival, GDPR)
Further Reading
- Database Internals - Alex Petrov
- Designing Data-Intensive Applications - Martin Kleppmann (Ch. 2-3)
- NoSQL Distilled - Pramod Sadalage & Martin Fowler
- Choosing a Database - Cockroach Labs
Related Notes
CAP Theorem: Understanding the Trade-offs
A deep dive into the CAP theorem, its implications for distributed systems, and how to choose the right trade-offs for your system.
Database Sharding
Horizontal partitioning of a database into independent shards — shard key selection, routing, replication with failover, and the operational costs.