Eqii
Development 10 min read

SQL vs NoSQL: Picking the Right Database

Relational vs document, key-value, and graph stores. ACID vs BASE, schema flexibility, scaling, and real-world examples (Postgres, MongoDB, Redis) — with a clear recommendation.

Why this comparison still matters

The SQL vs NoSQL debate peaked a decade ago, but the question it posed — should you store data in rigid, normalized tables or flexible, schemaless documents? — is still the first architectural decision for most applications. The market has matured: SQL databases have absorbed document features (Postgres JSONB), and NoSQL databases have added SQL-like query languages and transactions (MongoDB 4.0+ multi-document ACID). The lines have blurred, but the underlying trade-offs remain. This article compares them on data model, schema, consistency, scaling, and query flexibility.

SQL: the relational workhorse

SQL databases — PostgreSQL, MySQL, SQL Server, Oracle, SQLite — store data in tables of rows and columns, with relationships expressed via foreign keys. The query language is ANSI SQL, a standard older than most developers, with decades of optimization behind it. PostgreSQL's query planner, MVCC concurrency model, and extensive type system make it capable enough for everything from a small blog to a multi-terabyte analytics warehouse.

The relational model enforces schema at write time. A column has a type, constraints (NOT NULL, CHECK, UNIQUE, FOREIGN KEY) reject invalid data before it lands, and joins across tables let you express complex relationships declaratively. ACID transactions (atomic, consistent, isolated, durable) guarantee that a multi-step operation either fully completes or fully rolls back — critical for financial systems, inventory, and any write where partial failure would corrupt data.

Scaling SQL vertically is straightforward: bigger server, more RAM, faster disks. Scaling horizontally across multiple nodes is harder. Read replicas handle read-heavy workloads, but writes still go to a single primary. Distributed SQL (CockroachDB, YugabyteDB, Spanner) exists but adds latency and complexity. Sharding by application logic (e.g., users sharded by user_id hash) works but breaks cross-shard joins and transactions.

The payoff is correctness. A bank transfer debiting one account and crediting another must be atomic — SQL gives you that for free. Analytics on related data (orders joined to customers joined to products) is what SQL was designed for, and modern columnar engines (ClickHouse, DuckDB) make it screamingly fast.

NoSQL: flexible, distributed, specialized

NoSQL is not one technology but four broad families. Document stores (MongoDB, CouchDB) store JSON-like documents with flexible schemas; each document can have different fields. Key-value stores (Redis, DynamoDB) map keys to values with minimal query capability but extreme speed. Wide-column stores (Cassandra, HBase) partition data across nodes for massive write throughput. Graph databases (Neo4j, Dgraph) optimize for traversing relationships between entities.

The shared motivation is horizontal scalability. NoSQL databases were built assuming that no single machine is big enough, so data is partitioned (sharded) across many nodes by default. Cassandra replicates data across racks and data centers with tunable consistency; MongoDB's sharded clusters distribute documents by a shard key. This makes adding capacity a matter of adding nodes rather than buying a bigger server.

The cost is consistency. Most NoSQL databases favor eventual consistency (BASE — basically available, soft state, eventually consistent) over the strong consistency of ACID. A write may return success before all replicas have acknowledged it, so a subsequent read from a stale replica can return old data. MongoDB added multi-document ACID transactions in 4.0 (2018), and DynamoDB offers strongly consistent reads at extra cost, but the default in most NoSQL systems leans toward availability and partition tolerance per the CAP theorem.

Schema flexibility is a double-edged sword. No schema means you can ship a new field without a migration, but it also means bad data can accumulate silently — a typo in a field name creates a new field instead of an error. Most teams end up imposing schema at the application layer, which is rarely as rigorous as a database constraint.

Query flexibility varies by family. Document stores support rich queries and secondary indexes but struggle with multi-document joins. Key-value stores only look up by key — anything else requires application-side logic. Graph databases excel at relationship traversal (find friends-of-friends within 2 hops) but are poor at general-purpose queries.

CAP, consistency, and the transaction story

The CAP theorem (Brewer, 2000; formalized by Gilbert and Lynch in 2002) states that a distributed system can provide at most two of: consistency, availability, and partition tolerance. Since network partitions are inevitable in real systems, the practical choice is between CP (consistency plus partition tolerance) and AP (availability plus partition tolerance). Most SQL databases default to CP with strong consistency via primary-election replication — when the primary fails, the cluster elects a new primary, and during the election writes are unavailable. Most NoSQL databases default to AP, accepting eventual consistency to remain available during partitions.

The transaction story has converged. PostgreSQL has supported ACID since its inception, with MVCC (multi-version concurrency control) giving readers a consistent snapshot without blocking writers. MongoDB added multi-document ACID transactions in 4.0 (2018) and distributed transactions in 4.2 (2019), closing much of the gap for applications that need transactional guarantees on document data. Cassandra offers lightweight transactions via Paxos (expensive, use sparingly). DynamoDB offers transactional APIs for up to 25 items per request. The days when 'NoSQL means no transactions' are over — but the performance characteristics differ, and SQL databases remain the gold standard for complex multi-statement transactions.

When to pick a specific database: PostgreSQL is the right default for new applications — it handles relational, JSONB documents, full-text search (via tsvector), geospatial (via PostGIS), time-series (via TimescaleDB extension), and queue patterns (via SELECT ... FOR UPDATE SKIP LOCKED). Reach beyond Postgres only when you have a concrete need. MongoDB for document-heavy schemas with rapid evolution and a single-document access pattern. Redis for caching, session storage, rate limiting, leaderboards (via sorted sets), real-time analytics (via HyperLogLog), and pub/sub. Cassandra for time-series or write-heavy workloads across multiple data centers. Neo4j for graph traversal at the core of the product. Elasticsearch for full-text search at scale. ClickHouse or BigQuery for analytics over billions of rows. SQLite for embedded databases (mobile apps, single-node tools, application file formats).

Side-by-side comparison

DimensionSQLNoSQL
Data modelTables, rows, columns, foreign keysDocument, key-value, wide-column, graph
SchemaRigid, enforced at writeFlexible, often application-enforced
Query languageANSI SQL (standardized)Varies (MongoDB query language, Cypher, etc.)
JoinsNative, optimizedLimited or none
TransactionsACID, multi-statementOften eventual consistency; some support ACID
ScalingVertical primary; horizontal read replicasHorizontal by default (sharding, replication)
Consistency modelStrong (default)Tunable; often eventual
Schema migrationCoordinated ALTER TABLEAdd fields freely; migrations optional
ExamplesPostgreSQL, MySQL, SQLite, SQL ServerMongoDB, Redis, Cassandra, Neo4j, DynamoDB
Best forRelated data, transactions, analyticsFlexible data, massive scale, specialized access

When to choose which

Choose SQL — specifically PostgreSQL — when your data is relational, your writes need ACID guarantees, or you need complex queries and joins. This covers most applications: e-commerce (orders, customers, inventory), SaaS products (users, organizations, billing), financial systems, content management, and anything where data integrity matters. PostgreSQL's JSONB columns give you document-store flexibility when you need it, with the safety of SQL transactions when you don't. SQLite is the right answer for embedded databases, mobile apps, and small tools — it's the most-deployed database in the world for good reason.

Choose NoSQL when your data shape is genuinely unstructured or rapidly evolving, or when your workload matches a NoSQL family's specialty. Use MongoDB or CouchDB for content management systems where each document has a different shape, or for rapid prototyping where schema churn would slow you down. Use Redis for caching, session storage, real-time leaderboards, rate limiting, and pub/sub — anything that needs sub-millisecond reads and fits in memory. Use Cassandra or DynamoDB for high-write-throughput time-series or log data spread across regions. Use Neo4j when your core query is relationship traversal (social networks, fraud detection, recommendation engines).

Avoid NoSQL as a default just because it feels modern. Many teams pick MongoDB for a CRUD app, then spend months rebuilding joins in application code, fighting eventual consistency, and migrating to PostgreSQL when they realize their data is actually relational. Avoid SQL only when you have concrete scaling or flexibility needs it cannot meet — which, given modern PostgreSQL, is rarer than the marketing suggests.

A polyglot persistence pattern is increasingly common: PostgreSQL as the system of record for transactional data, Redis for caching and sessions, Elasticsearch for full-text search, and a columnar store (ClickHouse, BigQuery) for analytics. Each store does what it's best at, with pipelines keeping them in sync.

Conclusion

SQL and NoSQL are not opposites; they are points on a spectrum of trade-offs between consistency and flexibility, between normalized relations and denormalized documents. For most applications, start with PostgreSQL — it handles relational and document workloads, scales further than most teams need, and has the deepest tooling. Reach for a NoSQL store when your workload has a specific shape (key-value lookups, time-series writes, graph traversal) that SQL handles poorly. The best architectures use both, each for what it does best.