UUID (Universally Unique Identifier) generation seems trivial, but the choice of UUID version, storage format, and primary key strategy significantly affects database performance at scale. Here's what you need to know.
What is a UUID?
A UUID is a 128-bit identifier represented as 32 hexadecimal digits in five groups separated by hyphens:
``
550e8400-e29b-41d4-a716-446655440000
`
The format: 8-4-4-4-12 characters. Total: 36 characters including hyphens (32 hex digits + 4 hyphens).
The version (1–8) appears as the first digit of the third group. The variant bits appear in the fourth group.
For generating UUIDs quickly: use a UUID generator for individual IDs, or the command line for bulk generation.
UUID versions
v1 (timestamp + MAC address): Includes a timestamp and the machine's MAC address. Predictable (contains real MAC address), poor privacy. Rarely used in new systems.
v3 (namespace + MD5): Deterministic — the same input always produces the same UUID. Uses MD5 hashing. Suitable for generating consistent IDs from existing data (e.g., URL → UUID). MD5's collision resistance is weak; prefer v5 for new code.
v4 (random): 122 random bits. This is the most commonly used version — simple, universally supported, no predictability. The downside: purely random, so not time-ordered.
v5 (namespace + SHA-1): Like v3 but uses SHA-1. Use this when you need a deterministic UUID from a namespace and name.
v7 (Unix timestamp + random): New in RFC 9562 (2024). Encodes a 48-bit Unix millisecond timestamp in the most significant bits, followed by random bits. This makes v7 UUIDs monotonically increasing over time — the key advantage for database performance.
Why version matters for databases
UUID v4 inserts into B-tree indexes randomly. Each new row goes to a random position in the index, causing frequent page splits and random disk I/O. At high insert rates, this degrades performance significantly.
UUID v7 inserts are time-ordered. New rows go to the end of the index (since timestamps increase monotonically), similar to an auto-incrementing integer. This results in sequential I/O, dramatically better cache efficiency, and fewer index page splits.
Benchmark comparison (approximate, PostgreSQL):
- UUID v4: ~30–40% slower inserts vs. BIGINT at high volume
- UUID v7: near-parity with BIGINT for insert performance
- UUID v7 also reduces dead tuples from random page splits
For new systems handling significant insert volume, UUID v7 is increasingly recommended as the default primary key type.
Generating UUIDs
JavaScript (Node.js 14.17+ and browsers):
`javascript
// Built-in (no dependencies)
const id = crypto.randomUUID();
// → "550e8400-e29b-41d4-a716-446655440000"
// Node.js alternative
const { randomUUID } = require('crypto');
const id = randomUUID();
`
JavaScript with uuid npm package:
`javascript
import { v4 as uuidv4, v7 as uuidv7 } from 'uuid';
const id4 = uuidv4(); // v4 (random)
const id7 = uuidv7(); // v7 (time-ordered)
`
Python:
`python
import uuid
id4 = str(uuid.uuid4()) # v4 random
"f47ac10b-58cc-4372-a567-0e02b2c3d479"
Deterministic from namespace + name (v5)
id5 = str(uuid.uuid5(uuid.NAMESPACE_URL, "https://example.com"))
`
PostgreSQL (built-in support):
`sql
-- Generate a v4 UUID
SELECT gen_random_uuid();
-- Use as default for a column
CREATE TABLE users (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
email TEXT NOT NULL
);
`
MySQL/MariaDB:
`sql
-- Generate UUID (note: returns v1 by default)
SELECT UUID();
-- UUID without dashes
SELECT REPLACE(UUID(), '-', '');
`
Storage: string vs binary
String (VARCHAR(36) or CHAR(36)):
- Readable, easy to debug
- 36 bytes per UUID
- Slower index operations at scale
- Compatible with all tools and ORMs
Binary (BINARY(16)):
- 16 bytes per UUID (55% less storage)
- Faster index lookups — smaller index = more fits in cache
- MySQL 8+: built-in UUID_TO_BIN()
/ BIN_TO_UUID() functions - Requires conversion in application code
PostgreSQL native UUID type:
- Stored as 16 bytes internally
- Displays as 36-character string
- Best of both — use this when available
`
sql
-- PostgreSQL (recommended)
CREATE TABLE orders (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
customer_id UUID NOT NULL REFERENCES customers(id)
);
-- MySQL with binary storage
CREATE TABLE orders (
id BINARY(16) DEFAULT (UUID_TO_BIN(UUID())) PRIMARY KEY
);
`
When to use UUID vs auto-increment
Use auto-increment (BIGINT) when:
Single-database, no distributed systemMaximum insert performance is criticalNo need to know IDs before insert
Use UUID when:
IDs are generated client-side (offline-first apps)Merging data from multiple systemsExposing IDs in URLs (auto-increment IDs expose row count)Replication or sharding across multiple databasesYou don't want sequential IDs (security through obscurity)
Use UUID v7 specifically when:
You want UUIDs but also need insert performance comparable to BIGINTYou want IDs to be sortable by creation timeYou're building a new system and can choose freely
The nil UUID and max UUID
The nil UUID (all zeros): 00000000-0000-0000-0000-000000000000
Used as a sentinel value — "no UUID" or an uninitialized field. Never use it as a real identifier.
The max UUID (all ones): ffffffff-ffff-ffff-ffff-ffffffffffff`
Defined in RFC 9562. Used in range queries as an upper bound.
UUID v4 is the safe, universally supported default. If you're building a new system and database performance matters, consider UUID v7 from the start — retrofitting it later requires a migration.
Originally published at https://snappytools.app/uuid-generator/