Skip to main content

NewSQL Databases: CockroachDB, TiDB, and Google Spanner

Created: February 21, 2026 Larry Qu 3 min read

NewSQL databases combine the ACID guarantees of traditional databases with the horizontal scaling of NoSQL. This guide covers NewSQL concepts and leading implementations.

Understanding NewSQL

The Problem

traditional_sql:
  pros: "ACID, mature, well-understood"
  cons: "Can't scale horizontally"

nosql:
  pros: "Horizontal scaling, high throughput"
  cons: "Weak consistency, no ACID"

newsql:
  goal: "Best of both worlds"
  features:
    - "Distributed architecture"
    - "ACID transactions"
    - "Horizontal scaling"
    - "SQL interface"

CockroachDB

Architecture

cockroachdb:
  architecture: "Distributed, multi-active"
  consensus: "Raft consensus"
  replication: "3-way minimum"
  sharding: "Range-based automatic"
  sql: "PostgreSQL-compatible"
# Connect to CockroachDB
import psycopg2

conn = psycopg2.connect(
    host="localhost",
    port=26257,
    database="defaultdb",
    user="root",
    password="password"
)

# Create distributed table
cur = conn.cursor()

# Geo-partitioned table
cur.execute("""
    CREATE TABLE users (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        email STRING,
        region STRING,
        created_at TIMESTAMP DEFAULT now()
    ) PARTITION BY LIST (region) (
        PARTITION us VALUES IN ('us-east', 'us-west'),
        PARTITION eu VALUES IN ('eu-west', 'eu-east'),
        PARTITION default VALUES IN (DEFAULT)
    )
""")

# Transaction with retry
def transfer_funds(conn, from_id, to_id, amount):
    with conn.cursor() as cur:
        while True:
            try:
                cur.execute("BEGIN")
                cur.execute("""
                    UPDATE accounts 
                    SET balance = balance - %s 
                    WHERE id = %s AND balance >= %s
                """, (amount, from_id, amount))
                
                if cur.rowcount == 0:
                    cur.execute("ROLLBACK")
                    return False, "Insufficient funds"
                
                cur.execute("""
                    UPDATE accounts 
                    SET balance = balance + %s 
                    WHERE id = %s
                """, (amount, to_id))
                
                cur.execute("COMMIT")
                return True, "Transfer complete"
                
            except psycopg2.errors.SerializationFailure:
                cur.execute("ROLLBACK")
                continue

TiDB

Architecture

tidb:
  components:
    - "TiDB Server (SQL layer)"
    - "TiKV (storage engine)"
    - "PD (placement driver)"
  consensus: "Raft"
  features:
    - "HTAP (Hybrid Transactional/Analytical)"
    - "Automatic sharding"
    - "Strong consistency"
# TiDB with Python

import pymysql

conn = pymysql.connect(
    host="localhost",
    port=4000,
    user="root",
    password="password",
    database="test"
)

cur = conn.cursor()

# Create table with AUTO_RANDOM for distributed IDs
cur.execute("""
    CREATE TABLE orders (
        id BIGINT PRIMARY KEY AUTO_RANDOM,
        customer_id BIGINT NOT NULL,
        total DECIMAL(10,2),
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )
""")

# Distributed transaction (using optimistic locking)
cur.execute("""
    SELECT id, balance, version 
    FROM accounts 
    WHERE id IN (%s, %s)
    FOR UPDATE
""", (from_id, to_id))

# Update with version check
cur.execute("""
    UPDATE accounts 
    SET balance = balance - %s, version = version + 1 
    WHERE id = %s AND version = %s
""", (amount, from_id, from_version))

if cur.rowcount == 0:
    print("Concurrent modification detected")

Google Spanner

Architecture

spanner:
  provider: "Google Cloud"
  consistency: "TrueTime (GPS + atomic clocks)"
  features:
    - "Globally distributed"
    - "Strong consistency"
    - "Automatic sharding"
    - "Schema changes online"
# Google Spanner with Python

from google.cloud import spanner

client = spanner.Client(instance_id, database_id)

# Transaction with read-your-writes consistency
def update_inventory(instance_id, product_id, quantity):
    transaction = client.transaction()
    
    try:
        snapshot = transaction.read(
            table='inventory',
            columns=['product_id', 'quantity'],
            keys=[product_id]
        )
        
        row = list(snapshot)[0]
        new_quantity = row[1] + quantity
        
        transaction.update(
            table='inventory',
            columns=['product_id', 'quantity'],
            values=[(product_id, new_quantity)]
        )
        
        transaction.commit()
        
    except Exception as e:
        transaction.rollback()
        raise e

Comparison

Feature CockroachDB TiDB Spanner
License Open-source Open-source Proprietary
Storage RocksDB TiKV Colossus
SQL PostgreSQL MySQL PostgreSQL-like
Global Yes Yes Yes
HTAP Limited Yes Limited
Cloud Self-hosted + DBaaS Self-hosted + DBaaS GCP only

Use Cases

newsql_use_cases:
  - "Global e-commerce platforms"
  - "Financial transactions needing ACID"
  - "Multi-region applications"
  - "Real-time analytics + transactions"
  - "Replace sharded MySQL/PostgreSQL"

Best Practices

# NewSQL best practices

design:
  - "Choose right partition key"
  - "Avoid cross-partition transactions"
  - "Use serializable isolation carefully"
  - "Monitor latencies across regions"

performance:
  - "Place replicas near users"
  - "Batch operations when possible"
  - "Use follower reads for analytics"

Conclusion

NewSQL provides:

  • ACID: Strong consistency guarantees
  • Scaling: Horizontal scaling across regions
  • SQL: Familiar query interface

Choose based on deployment preference: self-managed (CockroachDB, TiDB) or managed (Spanner).


Comments

Share this article

Scan to read on mobile