SQL vs NoSQL — Financial Transactions Require ACID
Three production incidents in 4 months from a NoSQL migration: double-charges, analytics errors, schema bugs.
20+ years shipping high-throughput database systems. Written from production experience, not tutorials.
- ✓Solid grasp of fundamentals
- ✓Comfortable reading code examples
- ✓Basic production concepts
- SQL databases store data in structured tables with fixed schemas and support complex queries with JOIN — use for relational, transactional data
- NoSQL databases use flexible schemas (documents, key-value, wide-column, graph) — use for high-velocity writes, variable data shapes, or massive horizontal scale
- The decision driver: what shape is your data and how do you access it — not 'which is faster'
- SQL: strong consistency (ACID), complex queries, well-understood tooling, vertical scaling
- NoSQL: eventual consistency by default, simple access patterns, horizontal scaling by design
- Biggest mistake: choosing NoSQL because it 'scales better' without defining the access pattern first
Every application you have ever used — Instagram, your bank's website, Spotify, Uber — is powered by a database. SQL databases are like a perfectly organised spreadsheet: every row has the same columns, every column has a defined type, and you can ask complex questions across multiple sheets. NoSQL databases are like a filing cabinet where each folder can contain different things — one folder might have 3 documents, another might have 300, and each document looks completely different.
Every application you've ever used — Instagram, your bank's website, Spotify, Uber — is powered by a database. The choice of which type of database to use isn't just a technical detail. It's an architectural decision that shapes how your app scales, how fast it reads and writes data, and how painful future changes will be.
For most of the web's history, the answer was always SQL. Relational databases have decades of tooling, predictable behaviour, and robust consistency guarantees. The rise of NoSQL in the late 2000s introduced new trade-offs: more flexible schemas, built-in horizontal scaling, and access patterns that map directly to how applications actually query data.
This guide gives you the mental framework to make the right choice for your project — not based on hype, but based on your actual data shape and access patterns.
Why SQL Beats NoSQL for Financial Transactions
SQL databases enforce ACID (Atomicity, Consistency, Isolation, Durability) transactions, guaranteeing that a series of operations either all succeed or all fail as a single unit. NoSQL databases typically sacrifice ACID for horizontal scalability and flexible schemas, using eventual consistency instead. For financial transactions—where a single dollar must never be lost or double-counted—ACID is non-negotiable.
In practice, ACID isolation prevents phantom reads and write skews that can corrupt balances. A SQL transaction locks rows or uses multi-version concurrency control (MVCC) to ensure that two concurrent transfers don't both deduct from the same account. Without this, NoSQL's eventual consistency can lead to negative balances or duplicate payments. The trade-off: SQL writes are slower under extreme load, but correctness is guaranteed.
Use SQL for any system where money moves—ledgers, payments, trading platforms. NoSQL fits high-volume, low-stakes data like user sessions or product catalogs. Choosing NoSQL for financial transactions is a design error that will surface as reconciliation failures, audit nightmares, and lost revenue.
SQL Databases — The Relational Model and What It Gives You
SQL (Structured Query Language) databases organise data in tables with rows and columns. Every row in a table has the same columns. Every column has a defined data type. Foreign keys connect related tables. This structure is called the relational model and it was designed specifically to eliminate data redundancy and ensure consistency.
The relational model's biggest advantage is the query language. SQL lets you ask complex questions across multiple tables with JOINs, filter with WHERE, aggregate with GROUP BY, and sort and page with ORDER BY / LIMIT. These operations are built into the database engine and run efficiently regardless of which application framework is on top.
The transactional guarantees (ACID) mean that multiple related operations are atomic — either all of them succeed or none of them do. A bank transfer either moves money from both accounts or neither — no partial state. These guarantees are the reason relational databases have powered financial systems, e-commerce, and enterprise software for decades.
NoSQL Databases — Four Types, Four Use Cases
NoSQL is not one thing — it is a category that covers four fundamentally different database types, each optimised for a specific access pattern.
Document databases (MongoDB, CouchDB) store data as JSON-like documents. Each document can have different fields. Ideal when your data shape genuinely varies per record — an e-commerce product with 3 attributes for a book and 50 attributes for a TV.
Key-value stores (Redis, DynamoDB) store a value at a key. Lookups are O(1). No complex querying. Ideal for sessions, caches, leaderboards, and any access pattern that is always 'get the thing by ID.'
Wide-column databases (Cassandra, HBase) store data in column families, partitioned by a key. Optimised for high-velocity writes and large-scale time-series or event data. Access is always by partition key — complex joins are not supported.
Graph databases (Neo4j) store nodes and relationships. Ideal for social networks, recommendation engines, and any domain where the relationships between entities are the primary data.
Making the Decision — The Two-Question Framework
Most database selection debates waste time on the wrong questions. The right framework reduces to two questions.
Question 1: What shape is your data? If your data is relational — entities with well-defined foreign key relationships — SQL is almost always the right choice. If your data is genuinely variable per record (product attributes, user-generated content, configuration objects), a document store might be appropriate.
Question 2: What are your access patterns? If you query by many different attributes, use complex filters, and need aggregations — SQL and its query planner are built for this. If you always access data by a single key (user_id, device_id, session_token) and need extreme throughput — NoSQL is appropriate.
The secondary question: what scale are you actually at? Most applications operate at a scale where PostgreSQL with proper indexes handles the workload comfortably. The decision to add NoSQL should be driven by a measured problem — a specific query that cannot be made fast enough in PostgreSQL — not by anticipated future scale.
- PostgreSQL: transactional core — users, orders, payments, inventory
- Redis: session cache, real-time leaderboards, geospatial queries, rate limiting
- Cassandra/DynamoDB: high-velocity event streams, time-series, activity feeds
- Start with PostgreSQL only — add each additional database when you have a specific measured need
- Every additional database adds operational complexity: monitoring, backups, on-call runbooks
Scaling Up vs Scaling Out — Why Your Architecture Depends on This
Every developer eventually hits the wall. Your queries start timing out. The connection pool is exhausted. Your DBA is sending you passive-aggressive Slack messages about table locks.
SQL databases scale vertically. You throw more CPU, more RAM, a faster SSD at a single server. It works, until it doesn't. There's a ceiling on how big a single machine can get. And the price curve gets stupid fast.
NoSQL databases scale horizontally. You add more commodity servers to a cluster. Each node handles a slice of the data. Need more throughput? Add three more machines. No downtime. No schema changes.
But here's the catch — horizontal scaling trades away consistency guarantees. CAP theorem is not a suggestion. If you partition a SQL database across nodes, you either lose availability or consistency. NoSQL embraces eventual consistency by design.
Your choice isn't about which is "better." It's about which trade-off kills you slower. For most SaaS products, horizontal scaling is the right bet because user growth is unbounded. For financial ledgers, you pay for the vertical iron and you like it.
The Cost of a Join — And Why NoSQL Eliminates It
SQL databases are built around joins. Normalization means you split data across tables to avoid duplication. Every read then becomes a join operation. That's fine when your data fits on one server and your traffic is moderate.
But joins are expensive. The database has to scan indexes, build temporary tables, and sort results. A five-table join on a table with 10 million rows can take seconds. Your users don't wait seconds.
NoSQL eliminates joins by design. You store data the way you query it. If you need a user's profile and their last 20 orders, you store that as a single document. One read, one network round trip, one response.
This is denormalization. You duplicate data. Yes, it feels wrong if you've been trained on normalization rules. But in production, denormalization is often the difference between a 50ms API response and a 5-second timeout.
The trade-off: writes become more expensive. Every time an order is placed, you update the user document and the product document and the analytics document. NoSQL trades write complexity for read speed. Know your read-to-write ratio before you choose.
Why NoSQL Wins When Your Schema Is a Moving Target
SQL demands you know your schema before you write your first INSERT. Get it wrong? You're migrating tables, rewriting queries, and explaining to your PM why feature delivery just slipped two weeks. NoSQL laughs at your schema. Document stores let you add fields per record — one order has a 'discount_code', another doesn't. That's not bad design, that's real-world data.
Graph databases handle relationships you didn't know existed until you needed them. Key-value stores ignore structure entirely. The trade-off: you lose compile-time guarantees. You validate in application code instead of the database. That's fine — your test suite should catch schema drift before it hits production. When your product roadmap changes every sprint and the database fights you less than the frontend framework, you know you picked the right tool.
The Hidden Cost of Transactions — When ACID Breaks Your Back
ACID transactions sound like a superpower until you need to scale. Every BEGIN/COMMIT locks rows, fights contention, and throttles throughput. PostgreSQL can handle thousands of concurrent transactions — but that requires careful isolation levels, connection pooling, and index tuning. One long-running transaction blocks readers, cascading failures across your app.
NoSQL trades ACID for raw speed. Document databases guarantee consistency per document, but cross-document updates are YOUR problem. Graph databases let you traverse relationships atomically — until you need to update two nodes. Basecamp's shift from MySQL to a NoSQL-readiness pattern showed that your business logic can often handle eventual consistency. The question isn't 'Does my data need ACID?' It's 'Which operations truly can't tolerate stale reads?' For the rest, NoSQL's performance wins.
SQL vs NoSQL — Property Followed
The phrase "property followed" captures how each system tracks state changes. SQL databases follow the property of ACID: atomicity ensures transactions either fully complete or fully roll back, consistency enforces all data obeys schema rules, isolation prevents concurrent transactions from interfering, and durability guarantees committed data survives crashes. This property-followed model works when you must guarantee that money moves from account A to B without losing a penny. NoSQL databases follow a different property: BASE (Basically Available, Soft state, Eventual consistency). They prioritize availability over immediate correctness. In a NoSQL system, you accept that two users reading the same record at the same instant might see different values. The property followed by your choice determines the guarantees you can offer. SQL follows ACID for strict correctness; NoSQL follows BASE for speed and availability at scale.
Function of SQL — The Real Job of a Query Language
The function of SQL is not merely to retrieve data. Its primary function is to express set-based operations declaratively. You tell the database what data you want, not how to get it. This abstraction lets the query optimizer choose the fastest execution path — choosing indexes, join orders, and cache strategies. SQL’s function also includes enforcing structural integrity through constraints, controlling access with permissions, and managing concurrent writes with locking and MVCC. In practice, this means one JOIN statement can replace fifty lines of imperative code in Python or JavaScript. The function excels when relationships between data matter — when you need to ask "which customers bought this product last month" without writing a nested loop. NoSQL query languages (MQL, CQL, Gremlin) serve a different function: navigating hierarchical documents or graph edges directly, sacrificing set-based flexibility for locality of access.
NewSQL: CockroachDB, Yugabyte, TiDB as Hybrid Solutions
NewSQL databases aim to combine the scalability of NoSQL with the ACID guarantees of traditional SQL databases. They are designed for distributed environments where horizontal scaling is required but transactional integrity cannot be compromised. CockroachDB, Yugabyte, and TiDB are prominent examples, each implementing distributed SQL with strong consistency.
CockroachDB uses a distributed consensus protocol (Raft) to replicate data across nodes, ensuring ACID transactions even across geographic regions. It supports standard SQL and PostgreSQL-compatible syntax. For example, a financial transaction transferring funds between accounts can be executed as:
BEGIN; UPDATE accounts SET balance = balance - 100 WHERE id = 'A'; UPDATE accounts SET balance = balance + 100 WHERE id = 'B'; COMMIT;
This transaction is atomic, consistent, isolated, and durable, even if nodes fail. YugabyteDB similarly offers distributed ACID transactions with a PostgreSQL-compatible API, while TiDB uses a Percolator-style transaction model with Google Spanner-like capabilities.
These systems are ideal for applications that need to scale horizontally across multiple data centers without sacrificing transactional guarantees. However, they introduce complexity in deployment and may have higher latency for geographically distributed transactions due to consensus overhead. They are not a silver bullet but a powerful option for specific use cases like financial services, e-commerce, and multi-region applications.
Polyglot Persistence: Combining SQL and NoSQL in One Application
Polyglot persistence refers to using multiple database technologies within the same application to leverage the strengths of each. For example, you might use PostgreSQL for transactional data (e.g., orders, payments) and MongoDB for flexible, schema-less data (e.g., product catalogs, user sessions). This approach allows you to optimize for different data access patterns and consistency requirements.
Consider an e-commerce platform: financial transactions require ACID compliance, so they are handled by a SQL database. Meanwhile, product recommendations and user activity logs benefit from NoSQL's flexibility and scalability. The application code orchestrates data flow between databases, often using event-driven patterns or change data capture (CDC) to keep data synchronized.
A common pattern is to use a SQL database as the system of record and a NoSQL database as a query-optimized cache or read replica. For instance, after a transaction is committed in PostgreSQL, a change data capture tool like Debezium can stream the change to Elasticsearch for fast search or to Redis for caching. This reduces load on the primary database and improves read performance.
However, polyglot persistence introduces complexity in data consistency, operational overhead, and development effort. It requires careful design to avoid data duplication and ensure eventual consistency where needed. It is best suited for large-scale applications with diverse data needs, where the benefits outweigh the added complexity.
Decision Framework: When SQL, NoSQL, or NewSQL?
Choosing the right database depends on your application's requirements. Here's a framework to guide your decision:
- Data Consistency Requirements: If your application requires ACID transactions (e.g., financial systems, inventory management), prefer SQL or NewSQL. If eventual consistency is acceptable (e.g., social media feeds, analytics), NoSQL may suffice.
- Schema Flexibility: If your schema is stable and well-defined, SQL is a strong choice. If your schema evolves frequently or is unpredictable (e.g., IoT sensor data, content management), NoSQL's schema-less design is advantageous.
- Scalability Needs: For vertical scaling (scaling up) with moderate read/write loads, SQL works. For horizontal scaling (scaling out) with massive data volumes or high throughput, NoSQL or NewSQL is better.
- Query Complexity: If you need complex joins, aggregations, and ad-hoc queries, SQL excels. NoSQL is optimized for simple key-value lookups or document retrievals.
- Operational Overhead: SQL databases are mature with rich tooling. NoSQL databases often require more operational expertise. NewSQL adds complexity due to distributed coordination.
Example: For a banking application, use SQL (or NewSQL for global scale). For a real-time analytics dashboard, use NoSQL (e.g., Cassandra for time-series data). For a hybrid e-commerce platform, consider polyglot persistence with SQL for orders and NoSQL for product catalog.
Use this framework to evaluate trade-offs and avoid over-engineering. Start with a simple solution and evolve as needs grow.
A Document Database Migration Took 6 Months and Introduced Three Production Incidents
- Strong consistency is not optional for financial and inventory operations — choose your database accordingly
- Flexible schema is a double-edged sword — it delays schema discipline but does not eliminate the need for it
- Match the database to the domain: use SQL for relational transactional data, NoSQL for genuinely variable or high-velocity data
| File | Command / Code | Purpose |
|---|---|---|
| relational_model_example.sql | CREATE TABLE customers ( | SQL Databases |
| database_selection_decision.sql | SELECT | Making the Decision |
| ShardingNightmare.sql | CREATE TABLE orders_na ( | Scaling Up vs Scaling Out |
| JoinHell.sql | SELECT | The Cost of a Join |
| SchemaFlexibility.sql | CREATE TABLE orders ( | Why NoSQL Wins When Your Schema Is a Moving Target |
| ACIDCost.sql | BEGIN ISOLATION LEVEL SERIALIZABLE; | The Hidden Cost of Transactions |
| FollowedProperty.sql | BEGIN TRANSACTION; | SQL vs NoSQL |
| FunctionOfSQL.sql | SELECT c.name, o.total | Function of SQL |
| newsql_transaction.sql | BEGIN; | NewSQL |
| polyglot_example.sql | INSERT INTO orders (id, user_id, total, status) VALUES (1, 42, 99.99, 'pending')... | Polyglot Persistence |
| decision_framework.sql | CREATE TABLE transactions ( | Decision Framework |
Key takeaways
Interview Questions on This Topic
When would you choose a NoSQL database over PostgreSQL for a new feature?
Frequently Asked Questions
20+ years shipping high-throughput database systems. Written from production experience, not tutorials.
That's NoSQL. Mark it forged?
9 min read · try the examples if you haven't