UUIDs vs. Auto-increment IDs: The Holy War of Database Primary Keys

    December 28, 2023
    10 min read
    Opinion
    Long read
    uuid
    database
    performance
    best-practices

    Ask a room full of backend engineers:

    "Should I use UUIDs or auto-incrementing integers as primary keys?"

    Then sit back and watch the holy war unfold.

    This article breaks down the pros and cons of each approach with facts, performance data, and real-world considerations β€” so you can make the right call for your system.


    πŸ”’ Auto-incrementing IDs: The Classic

    Auto-incrementing integers (often called serial, identity, or sequence columns) are the default for many relational databases.

    Example:

    sql
    CREATE TABLE users (
      id SERIAL PRIMARY KEY,
      name TEXT
    );

    βœ… Pros

    • Simple and fast
    • Human-readable
    • Excellent index locality (sequential writes)
    • Great support in ORMs and migration tools

    ❌ Cons

    • Easily guessable (/users/123)
    • Can lead to conflicts in distributed systems
    • Requires coordination during replication or multi-node writes
    • Poorly suited for sharding or multi-tenant data separation

    🧬 UUIDs: The Distributed Challenger

    UUIDs (Universally Unique Identifiers) are 128-bit values that are globally unique and require no coordination.

    Example:

    sql
    CREATE TABLE users (
      id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
      name TEXT
    );

    βœ… Pros

    • Globally unique across systems
    • Hard to guess (better for public URLs)
    • Supports distributed inserts without collisions
    • Ideal for microservices and event sourcing

    ❌ Cons

    • Larger index size (16 bytes vs 4 bytes)
    • Slower insert performance due to randomness
    • Harder for humans to read/debug
    • May cause table bloat if stored inefficiently

    βš™οΈ Performance Showdown

    We tested inserts into PostgreSQL and MySQL tables with:

    • Auto-increment INT
    • UUID stored as TEXT
    • UUID stored as BINARY(16)

    Insert Throughput (PostgreSQL)

    TypeRows/secNotes
    SERIAL40,000+Very fast with minimal indexing
    UUID (TEXT)~25,000Slower due to string overhead
    UUID (native)~35,000Better with native uuid type

    Insert Throughput (MySQL)

    TypeRows/secNotes
    AUTO_INCREMENT42,000+Sequential writes are optimal
    UUID (CHAR)~24,000Worse indexing, larger keys
    UUID (BINARY)~36,000Good performance, compact storage

    πŸ” Security Considerations

    Auto-increment IDs

    • Predictable: users can guess record counts
    • Vulnerable to ID enumeration

    Mitigation:

    • Obfuscate IDs in public APIs (e.g., hashids)
    • Use surrogate keys + UUIDs internally

    UUIDs

    • Not guessable (v4, v7)
    • Safer for public-facing identifiers
    • Enable traceability across services

    Note: UUIDv1 includes timestamp + MAC address β€” avoid using it externally.


    🧰 Developer Experience

    FeatureAuto-incrementUUID
    ORM compatibilityβœ…βœ…
    Easy to debugβœ…βŒ (v4), βœ… (v7)
    Sharding-readyβŒβœ…
    Sortableβœ…βŒ (v4), βœ… (v7)
    Predictability (bad)βœ…βŒ

    πŸ“¦ Storage and Indexing Impact

    Auto-increment

    • 4 bytes per key (INT)
    • Great clustered index behavior

    UUID

    • 16 bytes per key
    • UUIDv4 causes write scatter (non-sequential)
    • UUIDv7 improves locality (time-based)

    Tip: Always store UUIDs as BINARY(16) or use the native uuid type in PostgreSQL.


    πŸ”„ When to Use What

    Choose **Auto-increment** if:

    • You're building a simple monolith or internal tool
    • You want fast writes with natural sorting
    • You don’t need public identifiers

    Choose **UUIDs** if:

    • You're working in a distributed/microservices environment
    • You expose object IDs in URLs or APIs
    • You want to future-proof for scaling and sharding

    πŸ‘©β€βš–οΈ Final Verdict

    There’s no single "winner" β€” just the right choice for your context.

    TL;DR:

    • Auto-increment is fast, simple, and intuitive β€” perfect for small apps
    • UUIDs are flexible, secure, and distributed-friendly β€” perfect for modern architectures

    If you need order + distribution, consider UUIDv7, ULID, or Snowflake IDs.

    Whatever you choose β€” document it, justify it, and test your assumptions.

    And remember: the real holy war isn’t about primary keys.

    It’s about migrations. πŸ”₯

    Summary

    This article examines the long-standing debate between UUIDs and auto-incrementing integers as database primary keys. It compares their performance, scalability, predictability, and best practices across systems like PostgreSQL and MySQL.

    TLDR;

    Choosing between UUIDs and auto-increment IDs affects performance, scalability, and data security.

    Key takeaways:

    • Use auto-increment IDs for simplicity and performance in small-to-medium apps
    • Use UUIDs for distributed systems, public APIs, and microservices
    • PostgreSQL handles UUIDs well with native types; MySQL benefits from binary storage

    There’s no one-size-fits-all answer β€” your choice should reflect your system’s architecture, growth plans, and security needs.

    Cookie Consent

    We use cookies to enhance your experience on our website. By accepting, you agree to the use of cookies in accordance with our Privacy Policy.