UUID v4 vs v7: Which One Should Your Database Use?
For years the advice on UUIDs as primary keys was "they're convenient, but they'll hurt your database." With UUID version 7, standardised in RFC 9562 in May 2024, that advice needs updating. Here's what changed and how to choose.
Why use UUIDs at all?
Auto-incrementing integers are small, fast and readable. UUIDs win in a few specific situations:
- Generating IDs before talking to the database. A mobile app working offline, or a service creating several related records in one go, can assign IDs itself.
- Merging data from multiple databases, shards or regions without collisions.
- Not leaking information.
/orders/1043tells a competitor roughly how many orders you've had, and invites people to try/orders/1042.
How v4 works
A version 4 UUID is 128 bits, of which 122 are random and 6 mark the version and variant. That's around 5.3 × 1036 possible values, so collisions are not a practical concern as long as the random number generator is good. Use crypto.randomUUID() in JavaScript or uuid.uuid4() in Python, not something built on Math.random.
The randomness is also v4's weakness as a primary key. Most databases store the primary key in a B-tree index ordered by value. Sequential IDs always go at the right-hand edge of the tree, so inserts touch the same few pages, which stay in memory. Random IDs land anywhere, so every insert can touch a different page. Pages split, the index becomes fragmented and larger than necessary, and once it no longer fits in memory, insert performance drops noticeably. On MySQL's InnoDB, where the table itself is stored in primary-key order, the effect is even more pronounced.
How v7 works
A version 7 UUID puts a 48-bit Unix timestamp in milliseconds at the front, followed by the version bits and about 74 bits of randomness:
0192 3f4a 7b2c 7 d91 a 3e5f 9b8c 0d1e 2f3a
└── timestamp ──┘ ver var └──── random ────┘
Because the timestamp comes first, IDs generated later sort after IDs generated earlier. New rows go to the end of the index, just like an auto-increment column, while you keep all the advantages of UUIDs. The random part still makes collisions practically impossible, even across many machines generating IDs in the same millisecond.
As a bonus, you can sort by ID to get rows in creation order, and you can extract the creation time from the ID if you ever need it. Try generating a few with the UUID generator and notice how the first characters barely change.
The trade-off: v7 reveals time
Anyone who sees a v7 UUID can read when it was created, to the millisecond. For most primary keys that's harmless. Creation dates are often visible anyway. But there are cases where it matters:
- Password-reset and email-verification tokens (use a dedicated random token, not any UUID, ideally).
- Share links where the creation time is sensitive.
- Anything where a sequence of IDs could reveal business volume over time.
For those, use v4, or better, a purpose-built random token of 128 bits or more.
Storage tips
- Use a native type. PostgreSQL's
uuidtype stores 16 bytes. Storing the 36-character string as text more than doubles the size and makes comparisons slower. In MySQL, useBINARY(16)withUUID_TO_BIN(). - Generate them where it's convenient. PostgreSQL 18 adds a built-in
uuidv7()function; older versions can use an extension or generate in application code. Most languages now have v7 support in their UUID libraries. - Don't parse the timestamp for business logic. Keep a real
created_atcolumn. The embedded timestamp is a property of the ID, not your source of truth.
What about ULID, Snowflake and friends?
ULID solved the same problem before v7 existed, with a similar layout and a shorter, Crockford Base32 text form. Twitter's Snowflake IDs pack a timestamp, a machine ID and a sequence number into 64 bits, which is smaller but needs coordination to assign machine IDs. If you already use one of these, there's no urgent reason to switch. For new projects, v7 has the advantage of being a standard that fits every existing UUID column and library.
Recommendation
| Situation | Choose |
|---|---|
| Primary keys in a new table | UUID v7 (or bigint auto-increment if you don't need UUIDs at all) |
| Public IDs where creation time should stay private | UUID v4 |
| Security tokens | A dedicated random token, 128+ bits, not a UUID |
| Existing v4 keys that perform fine | Leave them alone |
Generate random v4 or time-ordered v7 UUIDs, up to 1,000 at once.
Open the UUID Generator