Toolmingo
Guides13 min read

SQL vs NoSQL: Which Database Should You Use in 2025?

An in-depth comparison of SQL and NoSQL databases — covering data models, ACID vs BASE, performance, scalability, and which to choose for your project.

SQL and NoSQL represent two fundamentally different approaches to storing and querying data. SQL databases have powered applications since the 1970s. NoSQL databases emerged in the 2000s to solve specific scalability and flexibility problems that relational databases struggled with at web scale. Choosing the wrong one can cause major pain later — this guide covers every major dimension.

At a glance

SQL NoSQL
Data model Tables with rows and columns Documents, key-value, wide-column, graph
Schema Fixed schema (defined upfront) Flexible schema (dynamic)
Query language SQL (standardised) Database-specific APIs
Transactions Full ACID guarantees Varies (BASE to full ACID)
Scalability Vertical (scale up) + read replicas Horizontal (scale out natively)
Relationships Foreign keys, JOINs Typically embedded or application-handled
Consistency Strong consistency by default Eventual consistency common
Best for Structured data, complex queries, transactions Unstructured data, high throughput, flexibility
Examples PostgreSQL, MySQL, SQLite, SQL Server MongoDB, Redis, Cassandra, DynamoDB
Learning curve SQL is universal and transferable Each database has its own paradigm

What is SQL (relational)?

SQL databases store data in tables — rows and columns, like a spreadsheet, but with strict types and relationships enforced at the schema level.

-- Create a users table
CREATE TABLE users (
  id         SERIAL PRIMARY KEY,
  email      TEXT NOT NULL UNIQUE,
  name       TEXT NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now()
);

-- Create an orders table with a foreign key
CREATE TABLE orders (
  id         SERIAL PRIMARY KEY,
  user_id    INT REFERENCES users(id) ON DELETE CASCADE,
  total      NUMERIC(10,2) NOT NULL,
  status     TEXT DEFAULT 'pending'
);

-- Query with a JOIN
SELECT u.name, COUNT(o.id) AS order_count, SUM(o.total) AS total_spent
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id, u.name
ORDER BY total_spent DESC;

Core properties (ACID):

  • Atomicity — a transaction either fully completes or fully rolls back
  • Consistency — data always moves from one valid state to another
  • Isolation — concurrent transactions don't interfere with each other
  • Durability — committed data survives crashes

What is NoSQL (non-relational)?

NoSQL is an umbrella term for databases that don't use the relational model. There are four main types:

1. Document stores (MongoDB, Firestore, CouchDB)

Store data as JSON-like documents. Great for hierarchical, semi-structured data.

// MongoDB document
{
  _id: ObjectId("..."),
  email: "alice@example.com",
  name: "Alice",
  orders: [
    { total: 99.99, status: "delivered", items: ["book", "pen"] },
    { total: 24.50, status: "pending",   items: ["notebook"] }
  ],
  address: {
    city: "London",
    country: "UK"
  }
}

No JOINs needed — related data is embedded in one document. A single read gets everything you need for a user profile page.

2. Key-value stores (Redis, DynamoDB, Memcached)

The simplest model — a distributed hash map.

# Redis key-value
SET session:abc123 '{"userId":42,"role":"admin"}' EX 3600
GET session:abc123

ZADD leaderboard 1500 "alice"
ZRANGE leaderboard 0 9 REV WITHSCORES

Extremely fast for lookups by key. Used for caching, sessions, rate limiting.

3. Wide-column stores (Cassandra, HBase, Bigtable)

Tables with rows and columns, but columns are dynamic per row. Optimised for massive write throughput.

-- Cassandra CQL
CREATE TABLE messages_by_conversation (
  conversation_id UUID,
  created_at      TIMESTAMP,
  sender_id       UUID,
  body            TEXT,
  PRIMARY KEY (conversation_id, created_at)
) WITH CLUSTERING ORDER BY (created_at DESC);

Queries must include the partition key. No JOINs. Designed for time-series, IoT, messaging at massive scale.

4. Graph databases (Neo4j, Amazon Neptune)

Store data as nodes and edges. Perfect for relationship-heavy data.

-- Neo4j Cypher
MATCH (alice:User {name: "Alice"})-[:FOLLOWS]->(u:User)
WHERE NOT (alice)-[:FOLLOWS]->(u)-[:BLOCKED]->(alice)
RETURN u.name AS suggestion
ORDER BY u.follower_count DESC
LIMIT 10

ACID vs BASE

Property ACID (SQL) BASE (NoSQL)
Consistency Strong — guaranteed after commit Eventual — converges over time
Availability May sacrifice availability for consistency Prioritises availability
Isolation Transactions isolated (configurable level) Often no transactions across documents
Durability Durable on commit Usually durable (configurable)
Partition tolerance Typically CP (Consistent + Partition-tolerant) Typically AP (Available + Partition-tolerant)
Suitable for Financial, medical, e-commerce Social, IoT, analytics, caching

BASE = Basically Available, Soft-state, Eventually consistent.


Schema: fixed vs flexible

SQL: schema-first

You define the schema before inserting data. Changing it requires migrations.

-- Adding a column to a 50M-row table needs careful migration
ALTER TABLE users ADD COLUMN phone TEXT;
-- In production: use expand-contract pattern to avoid locking

Upside: the schema is your documentation. Invalid data cannot enter the database.
Downside: schema changes require planning and migration scripts.

NoSQL: schema-last (or schema-optional)

Documents don't need to match a fixed structure.

// These two documents can coexist in the same collection
{ _id: 1, name: "Alice", email: "alice@example.com" }
{ _id: 2, name: "Bob",   phone: "+44 7700 000000", tags: ["vip"] }

Upside: rapid iteration; no migration needed when adding fields.
Downside: inconsistent data; validation must happen at the application layer.


Performance comparison

Scenario SQL NoSQL
Simple key lookup Fast (indexed) Extremely fast (key-value O(1))
Complex JOIN queries Excellent (optimiser) Poor (no native JOINs)
Write throughput (millions/sec) Limited by ACID overhead Excellent (Cassandra, DynamoDB)
Aggregation / analytics Excellent (GROUP BY, window functions) Varies (MongoDB aggregation pipeline)
Full-text search Limited (basic LIKE, pg full-text) Built-in (Elasticsearch, MongoDB Atlas)
Time-series data Good with partitioning Excellent (Cassandra, InfluxDB)
Graph traversal Slow (recursive CTEs) Excellent (Neo4j, Neptune)
Caching Not designed for it Excellent (Redis microsecond latency)

Scalability

SQL scales vertically by default. Add more CPU and RAM to the database server. Read replicas help with read-heavy workloads. Horizontal sharding is possible but complex (PlanetScale, Citus for PostgreSQL).

NoSQL scales horizontally by design. Add nodes to a cluster. DynamoDB and Cassandra handle petabytes across hundreds of nodes with no single point of failure.

SQL typical scaling:              NoSQL typical scaling:
┌─────────────────┐               ┌────┐ ┌────┐ ┌────┐
│  Primary (RW)   │               │ N1 │ │ N2 │ │ N3 │
│  16 CPU / 64GB  │               └────┘ └────┘ └────┘
└────────┬────────┘                 add nodes as you grow
         │ replication
┌────────┴────────┐
│  Replica (R)    │
└─────────────────┘

Data model design: embedded vs normalised

One of the biggest SQL vs NoSQL decisions is where to handle relationships.

SQL: normalise, then JOIN

-- 3NF normalised schema
users(id, email, name)
products(id, name, price)
orders(id, user_id, created_at)
order_items(id, order_id, product_id, quantity)

-- Reconstitute with JOIN
SELECT o.id, u.name, p.name AS product, oi.quantity
FROM orders o
JOIN users u         ON u.id = o.user_id
JOIN order_items oi  ON oi.order_id = o.id
JOIN products p      ON p.id = oi.product_id
WHERE o.id = 42;

MongoDB: embed for read performance

// Embed order items inside the order document
{
  _id: ObjectId("..."),
  user: { id: "u42", name: "Alice" },
  items: [
    { product: "Book", qty: 2, price: 12.99 },
    { product: "Pen",  qty: 5, price: 1.49  }
  ],
  total: 33.43,
  created_at: ISODate("2025-01-15")
}
// Single document read — no JOIN needed
db.orders.findOne({ _id: orderId })

Rule of thumb:

  • Embed when the nested data is always read together and doesn't change independently
  • Reference (foreign key / $lookup) when data is shared across many parents or grows unboundedly

Transactions

SQL transactions

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- If anything fails here, both updates roll back automatically
COMMIT;

MongoDB multi-document transactions (v4.0+)

const session = client.startSession();
try {
  session.startTransaction();
  await accounts.updateOne({ _id: 1 }, { $inc: { balance: -100 } }, { session });
  await accounts.updateOne({ _id: 2 }, { $inc: { balance:  100 } }, { session });
  await session.commitTransaction();
} catch (err) {
  await session.abortTransaction();
} finally {
  session.endSession();
}

MongoDB supports multi-document transactions, but they carry performance overhead. The preferred MongoDB pattern is to design your schema so transactions are unnecessary — embed related data in one document.

Cassandra: no multi-row transactions

Cassandra supports lightweight transactions (compare-and-set via Paxos) for single partition operations, but has no general multi-row transactions. Design your data model around this constraint.


When SQL wins

Situation Why SQL
Financial data (banking, payments) ACID guarantees; no lost transactions
Complex relationships (ERP, CRM) JOINs are efficient; normalisation prevents anomalies
Reporting and analytics GROUP BY, window functions, CTEs, subqueries
Regulatory / compliance Audit trails; referential integrity enforced by DB
Team knows SQL SQL is universal — hire any backend engineer
Unknown query patterns SQL's flexibility handles ad-hoc queries well
Medium data scale (up to ~1TB) PostgreSQL handles this comfortably

When NoSQL wins

Situation Why NoSQL
Massive write throughput Cassandra, DynamoDB handle millions of writes/sec
Document storage (CMS, catalogs) MongoDB embeds nested data naturally
Caching / sessions Redis gives microsecond latency
Real-time features (chat, pub/sub) Redis Pub/Sub, Firestore listeners
Flexible schema (early startup) Add fields without migrations
Global, multi-region DynamoDB Global Tables, Cassandra multi-DC
Graph data (social network, fraud detection) Neo4j traverses deep relationships efficiently
Time-series / IoT Cassandra, InfluxDB handle high-frequency writes

Popular databases side-by-side

Database Type ACID Best for
PostgreSQL Relational Full General-purpose, JSON, analytics
MySQL / MariaDB Relational Full Web apps, WordPress, e-commerce
SQLite Relational Full Embedded, mobile, development
SQL Server Relational Full Microsoft ecosystems, enterprise
MongoDB Document Multi-doc (v4+) CMS, product catalogs, user profiles
Redis Key-value Partial (Lua) Cache, sessions, leaderboards, pub/sub
Cassandra Wide-column LWT only Time-series, IoT, messaging at scale
DynamoDB Key-value + doc Per-partition AWS serverless, global scale
Elasticsearch Document No Full-text search, log analytics
Neo4j Graph Full Social graphs, fraud detection
InfluxDB Time-series No Metrics, monitoring, IoT
Firebase / Firestore Document Per-document Mobile apps, real-time sync

SQL vs NoSQL: full comparison

Dimension SQL NoSQL
Data model Tables, rows, columns Documents, key-value, wide-column, graph
Schema Rigid, enforced Flexible, optional
Query language SQL (ISO standard) Database-specific
JOINs Native, optimised Rare (embed data instead)
Transactions Full multi-table ACID Varies widely
Consistency Strong Eventual (usually)
Write scalability Vertical + sharding (complex) Horizontal (built-in)
Read scalability Read replicas Horizontal + eventual consistency
Schema migration Required, tooling mature Not needed (or optional)
Developer experience Universal SQL knowledge Learn each DB separately
Cost at scale Expensive (large vertical) Cheaper (commodity nodes)
Data integrity Enforced by DB Application responsibility
Indexing B-tree, GIN, GiST, etc. Varies (compound, sparse, TTL)
Maturity 50+ years 15–20 years
Managed cloud RDS, Cloud SQL, Supabase, Neon DynamoDB, MongoDB Atlas, Upstash
Open source PostgreSQL, MySQL, MariaDB MongoDB (SSPL), Redis, Cassandra

The "both" approach

Modern applications commonly use both:

┌─────────────────────────────────────────┐
│              Your Application           │
└────────────────────┬────────────────────┘
                     │
     ┌───────────────┼───────────────┐
     ▼               ▼               ▼
┌─────────┐   ┌───────────┐   ┌──────────┐
│PostgreSQL│   │   Redis   │   │  Elastic │
│ (primary │   │  (cache + │   │  search  │
│  store)  │   │  sessions)│   │ (search) │
└─────────┘   └───────────┘   └──────────┘

Example: an e-commerce platform might use:

  • PostgreSQL for orders, inventory, and payments (ACID required)
  • Redis for sessions, cart data, and product page caching
  • Elasticsearch for product search with fuzzy matching
  • MongoDB for product reviews with varying attributes

Common mistakes

Mistake Why it's a problem Fix
Using NoSQL to avoid learning SQL SQL is essential; you'll hit limitations fast Learn SQL regardless
Storing everything in one MongoDB collection Becomes a chaos of inconsistent documents Use schema validation; separate collections
JOINing in NoSQL with $lookup for every query Slow; defeats the purpose of document storage Redesign schema to embed
Using SQL for a hot-path cache Unnecessary load; RDBMS not optimised for it Add Redis for caching layer
No transactions in MongoDB when you need them Data corruption on failure Use sessions+transactions or redesign schema
Horizontal sharding SQL prematurely Massive complexity before you need it Start vertical; shard when proven necessary
Assuming NoSQL = schemaless = no validation Application receives garbage data Add JSON Schema validation (MongoDB) or Zod
Picking Cassandra for a small project Complex ops, limited query flexibility Use PostgreSQL until you need Cassandra scale

Decision guide

Use SQL (PostgreSQL) when:

  • You have relational data with many-to-many relationships
  • You need complex queries you can't predict upfront
  • You're building anything financial or requiring audit trails
  • Your team knows SQL
  • Data fits on one server (up to a few TB)

Use a document store (MongoDB) when:

  • Your data is naturally hierarchical (nested objects)
  • Schema varies per record (product catalog with different attributes)
  • You're prototyping and need schema flexibility
  • You want a single-document read for your main entity

Use a key-value store (Redis) when:

  • You need sub-millisecond latency for lookups
  • Data is ephemeral (sessions, caches, rate limiting)
  • You need pub/sub, sorted sets, or atomic counters

Use a wide-column store (Cassandra / DynamoDB) when:

  • You need millions of writes per second
  • Data is time-series or event-driven
  • You need global, multi-region distribution

Use a graph database (Neo4j) when:

  • The data is fundamentally relational in complex ways (friends-of-friends, fraud rings)
  • You need deep traversals (more than 3 hops are slow in SQL)

Default choice: PostgreSQL. It handles 90% of use cases, supports JSONB for document-style storage, has excellent horizontal read scaling via replicas, and has a massive ecosystem. Add Redis for caching. Add Elasticsearch for search. Reach for specialised NoSQL only when you have a concrete need.


FAQ

Q: Can NoSQL databases be ACID compliant?
Yes. MongoDB supports multi-document ACID transactions (v4.0+). FaunaDB (now Fauna) was designed with ACID from the start. DynamoDB supports transactions within a table. However, most NoSQL databases make trade-offs — they default to eventual consistency because it enables higher availability and write throughput.

Q: Is SQL dead?
No. PostgreSQL is one of the fastest-growing databases in 2025. SQL skills are arguably more in demand than ever — including for querying Snowflake, BigQuery, Redshift, and even MongoDB (via Atlas SQL). The "NoSQL killed SQL" narrative from the 2010s didn't materialise.

Q: Can PostgreSQL replace MongoDB?
For most use cases, yes. PostgreSQL's JSONB column type supports indexing, querying, and storing arbitrary JSON documents. pg_jsonschema adds schema validation. For true MongoDB feature parity at massive document scale (>100M documents, complex aggregations), MongoDB still has advantages. But for most teams, PostgreSQL + JSONB avoids the operational complexity of running two databases.

Q: Does Google / Netflix / Amazon use SQL or NoSQL?
Both. Google built Spanner (distributed SQL, global ACID). Netflix uses Cassandra for billions of events. Amazon uses DynamoDB for Prime Day traffic (hundreds of millions of calls). Every hyperscaler runs dozens of database systems tailored to specific use cases. The lesson: there is no single right answer.

Q: What about NewSQL databases?
NewSQL databases (CockroachDB, TiDB, Spanner, YugabyteDB, PlanetScale) offer SQL semantics with horizontal scalability. They're a middle ground: you write standard SQL, but the database distributes data across nodes like a NoSQL system. Good choice when you need PostgreSQL semantics at Cassandra scale.

Q: When should I switch from SQL to NoSQL?
Concrete signals to consider migrating:

  • Your PostgreSQL server consistently hits CPU/memory limits and vertical scaling is no longer cost-effective
  • You're doing more than ~100k writes/second on a single table with no relief from partitioning
  • Your data is fundamentally non-relational (e.g., sensor readings with unpredictable fields, social graph traversals beyond 3 hops)
  • You need global multi-region active-active writes

Don't migrate speculatively. Start with PostgreSQL.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools