UUID v4 vs v7: random or time-ordered, and which one your database wants
Choosing a primary key looks like a five-minute decision and quietly determines how a table behaves at ten million rows. Auto-increment integers are compact and fast, and force every ID to be allocated by one database. Random UUIDs can be generated anywhere with no coordination — and if you make one the clustered primary key of a large table, write throughput degrades in a way that is genuinely hard to diagnose after the fact. UUIDv7 exists to keep the first property while fixing the second. This guide explains what is actually inside each version, the index behaviour that makes the difference, what v7 gives away in exchange, and how to pick — with the UUID generator here producing both kinds if you want to look at real values while you read.
What a UUID is made of
A UUID is 128 bits, written as 32 hexadecimal characters in five hyphenated groups. Two positions are not random in any version — they identify the version and the variant:
018f4d6c-9a3e-7c21-b8f4-2e1d5a9c7b03
^ ^
| └── variant: first hex digit is 8, 9, a or b
└────── version: this digit is the version numberSo you can read any UUID at a glance. The 13th hex character is the version: 4 for random, 7 for time-ordered, 1 for the old MAC-address-and-timestamp scheme. The 17th is the variant, which for every UUID you will meet is 8, 9, a or b. The formal specification is RFC 9562, published in 2024, which replaced RFC 4122 and added versions 6, 7 and 8.
v4: 122 bits of randomness and nothing else
A v4 UUID is random apart from the six fixed bits, giving 122 bits of entropy. Any process anywhere can generate one with no coordination and no realistic chance of a clash — the birthday bound puts a 50% chance of one collision at roughly 2.3 × 1018 values. That independence is the whole point: a mobile client can create a record offline, two systems can merge their data without renumbering, and an ID can be generated before the row is written.
What matters far more than collisions is where the randomness comes from. A UUID built with Math.random()or a seeded PRNG is neither unique nor unguessable, and there have been real vulnerabilities where predictable “random” identifiers let one user enumerate another’s records. The generator on this site uses the browser’s cryptographic random source for both versions, which is the only acceptable choice for anything that ends up in a URL.
Why random keys make databases unhappy
Database indexes are B-trees, and B-trees are happiest when new keys arrive in ascending order: each insert lands at the right-hand edge, in a page that is already in memory, and full pages are simply left behind. Sequential inserts therefore touch one hot page and fill their pages to capacity.
Random keys invert every one of those properties:
- Every insert lands in a different page. With a large table, that page is probably not in the buffer pool, so an insert becomes a read before it becomes a write.
- Pages split in the middle. Inserting into the interior of a full page splits it into two half-full pages, so the index occupies far more space than the data warrants and fits into memory correspondingly worse.
- The working set becomes the whole index. Sequential inserts keep a handful of hot pages cached; random inserts spread writes across everything, which is why the problem is invisible on a small table and severe on a large one.
MySQL’s InnoDB feels this most sharply, for two structural reasons: the table is the primary-key B-tree (a clustered index), so a random PK randomises the physical layout of the data itself, and every secondary index stores a copy of the primary key — so a 16-byte random key inflates every other index too. Store it as CHAR(36) instead of BINARY(16), as a great many schemas do, and you have made each of those copies 36 bytes.
Postgres does not cluster tables by primary key, so the effect is milder, but the index still fragments and the cache still churns. SQL Server clusters by default, so it behaves like InnoDB — which is why NEWSEQUENTIALID() exists at all.
v7: a timestamp in front of the randomness
UUIDv7 keeps the shape and swaps the content of the first half:
48 bits Unix time in milliseconds, big-endian
4 bits version (0111)
12 bits random
2 bits variant (10)
62 bits random
───────
128 bits, of which 74 are randomBecause the timestamp occupies the most significant bits, sorting v7 UUIDs as bytes — or as text, since hex preserves order — sorts them by creation time. Newly generated values are therefore almost ascending, which restores the right-hand-edge insert pattern the B-tree wants, while 74 random bits keep values unguessable and safe to mint on any number of machines at once. Within a single millisecond, ordering between values is random; that is rarely a problem, and implementations that need strict monotonicity use some of the random bits as a counter.
The practical payoff: v7 gives you decentralised generation with index behaviour close to a sequence. For a new table with UUID keys and meaningful write volume, it is simply the better default. Generate a handful of each with the tool and the difference is visible immediately — a column of v4s is unordered noise, while a column of v7s shares a long common prefix that ticks upward as you watch.
What you give up
The timestamp is not obfuscated. Anyone holding a v7 UUID can extract the creation time to the millisecond — copy the first twelve hex characters, read them as a millisecond value, and the timestamp converter will tell you the date. Consider whether that matters for your data:
- Usually fine. An order ID whose creation time is visible to the customer who placed the order reveals nothing new, and being able to sort or range-scan by ID is genuinely useful.
- Sometimes not.Two IDs from different records reveal how close together the records were created, which can leak business volume, or in a sensitive context the timing of a user’s activity. Where that matters, use v4 — or keep a v7 internal key and expose an unrelated random token.
Note what v7 does notleak, unlike the old v1: there is no MAC address in it. Version 1 embedded the network card’s hardware address, which is how the author of the Melissa virus was identified from a document in 1999, and is why v1 should be considered obsolete rather than merely old.
The other versions, briefly
| Version | Content | Use |
|---|---|---|
| v1 | Timestamp + clock sequence + MAC address | Legacy. Leaks hardware identity; MySQL’s UUID() still returns these |
| v3 / v5 | MD5 / SHA-1 hash of a namespace plus a name | Deterministic IDs — the same input always yields the same UUID. Useful for idempotent imports; prefer v5 |
| v4 | 122 random bits | The default for twenty years; still right when the value must reveal nothing |
| v6 | v1 with the timestamp reordered to sort correctly | A migration path for systems already on v1 |
| v7 | Unix ms + 74 random bits | The recommended choice for new database keys |
| v8 | Whatever you define | A sanctioned space for custom schemes that still look like UUIDs |
Outside the UUID family, ULID and KSUID solve the same problem with friendlier text encodings (26 and 27 characters of base32/base62 rather than 36 with hyphens), and Snowflake IDs pack a timestamp, machine number and sequence into 64 bits — half the size, at the cost of needing coordinated machine IDs. All three predate v7 and were, in effect, the field trials for it. If you are starting now and using a database with UUID support, v7 is the one with a standard behind it.
Storing them properly
- PostgreSQL: the native
uuidtype, 16 bytes.gen_random_uuid()is built in for v4, and version 18 addeduuidv7(). - MySQL / MariaDB: no UUID type — use
BINARY(16)withUUID_TO_BIN()andBIN_TO_UUID(). The optional second argument that swaps time fields exists to make v1 values sortable and should not be used with v7, which is already in the right order. - SQL Server:
uniqueidentifier. If you cluster on it, either use v7 or make the clustered index something else and keep the UUID as a non-clustered unique key. - Anywhere: if a UUID must be a clustered or primary key on a big table, prefer v7. If you are stuck with v4 on a hot table, the standard escape is to cluster on a sequence and keep the UUID as a unique secondary key.
Using the generator
Pick a version, choose how many you want — up to 1,000 in one go — and generate. The values appear in a text box you can copy wholesale or download as a .txtfile, which is the fastest way to produce seed data, fixtures or a batch of test identifiers. Both versions use the browser’s cryptographic randomness: v4 comes from the platform’s own UUID function, and v7 is built to the RFC 9562 layout with a real millisecond timestamp, so the values are usable as-is rather than being illustrative. As with everything here, generation happens in the page — the identifiers are not produced on a server that could keep a copy.
Do this
- Use v7 for new database keys — you keep decentralised generation and get index behaviour close to a sequence.
- Use v4 when the identifier must reveal nothing at all, including when it was created.
- Store UUIDs binary: the native type in Postgres and SQL Server,
BINARY(16)in MySQL. NeverCHAR(36)as a key. - Never generate a UUID from a non-cryptographic random source.
- If a hot table already has a random clustered key, cluster on a sequence and keep the UUID as a unique index.
- Prefer a bigint sequence when IDs never leave one database and nothing external mints them.
Frequently asked questions
Will UUIDs ever collide?
Not in any system you will build. A v4 UUID has 122 random bits, and by the birthday bound you would need to generate roughly 2.3 × 10^18 of them before the chance of a single collision reaches 50%. At a million a second that is about 73,000 years. The realistic risks are a broken random source or an application that generates the value in two places, not the mathematics.
Does UUIDv7 leak information?
It reveals the millisecond at which the value was created, because the first 48 bits are a Unix timestamp in plain sight. That is usually harmless and sometimes useful for debugging, but it does mean an ID exposed in a URL tells the holder when the record was made — and lets an observer infer creation rates. The remaining 74 bits are random, so v7 identifiers are still unguessable.
Should I store UUIDs as text or binary?
Binary, wherever your database has a real type for it. Postgres has a native uuid type; SQL Server has uniqueidentifier; MySQL and MariaDB have no UUID type, so use BINARY(16) with UUID_TO_BIN/BIN_TO_UUID. Storing a UUID as CHAR(36) more than doubles the size of the key and of every secondary index that carries it.
Is UUIDv7 a standard, or a draft?
A standard. RFC 9562, published in 2024, replaced the old RFC 4122 and formally defines versions 6, 7 and 8 alongside the familiar ones. Library and database support followed quickly — PostgreSQL added a built-in uuidv7() function in version 18, and most language ecosystems have had implementations for a while.
When is a plain auto-increment integer still the right choice?
When the IDs never leave one database and no client ever mints one. An 8-byte bigint is half the size of a UUID, indexes perfectly, and is easier to read in logs. Its costs are that it needs a round trip to allocate, it leaks volume through a guessable sequence, and it collides horribly when you merge two datasets — which is exactly when UUIDs start paying for themselves.
Tools used in this guide
Every one of these runs in your browser — the files you work on never leave your device.