⚡Utilvy
2026-10-26

UUID vs GUID vs ULID: Which to Use in Your Database

A UUID is a 128-bit identifier, a GUID is Microsoft's name for the same idea, and a ULID is a newer 128-bit ID that sorts itself by creation time. For most databases the choice is simple: use UUIDv4 by default, switch to UUIDv7 when insert performance matters, and pick ULID when you want time-sortable, shorter IDs.

What UUID, GUID and ULID Actually Mean

UUID stands for Universally Unique Identifier. It is a 128-bit value defined by RFC 9562 (the successor to RFC 4122) and usually written in canonical form with hyphens, like 550e8400-e29b-41d4-a716-446655440000. The version number sits in the string itself: 41d4 above means version 4. The two versions you will actually meet in databases are:

  • UUIDv4 — 122 random bits. Every ID is unpredictable and collisions are astronomically unlikely (you would need to generate billions per second for centuries to hit one).
  • UUIDv7 — a 48-bit Unix timestamp in milliseconds plus 74 random bits. The timestamp lives in the high bits, so IDs come out in creation order.

GUID (Globally Unique Identifier) is simply Microsoft's term for the same 128-bit structure. SQL Server's uniqueidentifier, Windows registry entries and .NET's Guid.NewGuid() all produce GUIDs. In practice the words are interchangeable — a GUID is a UUID generated by Microsoft tooling, so old GUID strings sometimes look "shuffled" only because some Microsoft formats stored the first three fields little-endian.

ULID (Universally Unique Lexicographically Sortable Identifier) packs the same 128 bits differently: 48 bits of millisecond timestamp followed by 80 random bits, encoded in Crockford Base32 as 26 characters instead of 36. It has no hyphens, sorts correctly as plain text, and its timestamp is deliberately decodable — a ULID like 01J5ZC7Q9T3WJ8N4P2R6S8V0WX starts with an extractable encoded timestamp.

Here is how the three compare side by side:

| Property | UUID v4 | UUID v7 | GUID | ULID | |---|---|---|---|---| | Size | 128 bits | 128 bits | 128 bits | 128 bits | | Built from | Pure random | Timestamp + random | Vendor-specific (usually random) | Timestamp + random | | Sorts by creation time | No | Yes | No | Yes | | Text length | 36 chars | 36 chars | 36 chars | 26 chars | | Standard | RFC 9562 | RFC 9562 | Microsoft convention | Community spec | | Case | Lowercase hex | Lowercase hex | Often uppercase | Uppercase Base32 | | Example | 9f86d081-8847-4d73-9e6e-… | 018f3a6b-7c1d-7xxx-… | 9F86D081-8847-4D73-… | 01J5ZC7Q9T3WJ8N4P2R6S8V0WX |

Why the Sort Order Matters for Indexes

The practical difference between v4 and v7/ULID is not uniqueness — they are all effectively collision-free. It is where new rows land in the index.

A B-tree index keeps keys sorted. With random UUIDv4 keys, each new row lands at a random position across millions of index pages. On a 10-million-row table, every insert touches a page that is probably not in memory, causing random writes, page splits and buffer-pool churn. This is why SQL Server's uniqueidentifier clustered indexes fragmented so badly that Microsoft had to add special handling for them.

With UUIDv7 or ULID, the millisecond timestamp means every new key is greater than the previous one, so inserts always hit the right edge of the index — sequential writes, minimal page splits, and better cache hit rates. For a log table receiving thousands of inserts per second, this alone can double write throughput.

The trade-off: time-ordered IDs leak creation time to anyone who sees them. If your order IDs appear in a URL, a competitor can decode 018f3a6b… and estimate when the record was created — and roughly how many records were created around it.

Step-by-Step: Choosing and Generating IDs

Step 1 — Decide whether you need time order. If the table is small (under a few million rows), insert speed differences are negligible and UUIDv4 is fine. If it is a high-write table (events, logs, messages), choose v7 or ULID.

Step 2 — Check your database's native support.

  • PostgreSQL 16+: SELECT uuidv7(); is built in; older versions can use the pg_uuidv7 extension.
  • MySQL 8 / SQL Server: no native v7 — generate it in application code or use the uuid-ossp-style routines for v4.
  • MongoDB / DynamoDB / Cassandra: generate in application code — the database does not care which version you pick.

Step 3 — Generate in application code. Modern runtimes cover the basics:

// Node.js 19+
crypto.randomUUID();                     // UUID v4
// npm install uuid
import { v7 as uuidv7 } from "uuid";
uuidv7();                                // UUID v7
# Python: pip install ulid-py
import ulid, uuid
uuid.uuid4()                             # UUID v4
str(ulid.new())                          # ULID, 26 chars

Step 4 — Keep the column type right. In Postgres, store UUIDs as uuid (16 bytes binary), never as text (36+ bytes). For ULIDs in a uuid column, strip the dashes first — a ULID is exactly 128 bits, so it fits perfectly: convert the Base32 to hex, or simply use a text/char(26) column if you want the human-readable form in queries and logs.

Step 5 — Migrate carefully. New ID schemes only apply to new rows. Backfilling old rows with v7 IDs destroys their meaning (the timestamp no longer reflects creation), so keep existing v4 keys as they are — the index only cares about new keys being ordered.

Are GUID and UUID the same thing?

Functionally yes — both are 128-bit identifiers, and the terms are used interchangeably. "GUID" is the label Microsoft uses in SQL Server, Windows and .NET; "UUID" is the standards term from RFC 9562. If someone says their database uses GUIDs, they mean UUIDs generated by Microsoft tooling.

Is UUID v7 safe to expose in URLs?

It depends on how much you care about leaking metadata. A v7 UUID's first 48 bits are a millisecond Unix timestamp, so anyone with the ID can decode exactly when the record was created. That is usually harmless, but for anything sensitive — ticket IDs, invoices, user identifiers — the timestamp plus issuance rate can reveal business activity. In those cases stay with v4, or keep v7 internally and expose a separate random public slug.

Can I store a ULID in a UUID column?

Yes. Both are 128 bits, so a ULID fits into any uuid/uniqueidentifier column — you just convert the Crockford Base32 text into the dashed hexadecimal form. Many teams do exactly this to keep a binary 16-byte column while generating ULIDs in code. The downsides: you lose plain-text sortability when reading raw values, and mixing v4 UUIDs with ULID-derived values in one column means two different visual formats.

The Bottom Line

UUIDv4 remains the correct default: random, proven, supported everywhere. Reach for UUIDv7 when insert-heavy tables start paying the random-write tax, and consider ULID when you also want shorter, text-sortable IDs or decodable timestamps. Whichever you choose, generating and validating them is trivial with a free UUID generator — and since UUIDv7 and ULID timestamps are just Unix milliseconds, you can decode any ID's creation time with a Unix timestamp converter.

Try these tools