← Home ← Codex ← DEBT ← Engine
Browse by Category
+ added · ✎ updated 7d
← Back to glossary

UUID vs ULID vs Auto-Increment

Database PHP 7.0+ Intermediate
debt(d7/e5/b7/t7)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The term's detection_hints lists percona-toolkit and mysql-slow-query-log, both runtime/operational tools rather than static analysis. Index fragmentation from UUID v4 only manifests under load, and auto-increment enumeration vulnerabilities require manual security review. No automated static tool catches these patterns at development time.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix suggests switching from UUID v4 to UUID v7/ULID, but this requires migrating existing data, updating schema definitions, changing ID generation code, and potentially modifying foreign key relationships. On large tables, this is a significant migration effort within the database layer, not a one-line fix.

b7 Burden Structural debt — long-term weight of choosing wrong

Closest to 'strong gravitational pull' (b7). Primary key strategy is a foundational database design choice that affects every table, every foreign key relationship, and every query pattern. The term applies_to all PHP contexts (web, cli, queue-worker), indicating system-wide reach. Once you've committed to UUID v4 on high-write tables, the fragmentation tax is paid on every INSERT and the migration cost shapes future decisions.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7). The misconception explicitly states that UUID v4 is commonly assumed to be the best choice for globally unique IDs, when its randomness actually causes severe B-tree index fragmentation. This contradicts intuition from other ecosystems where UUIDs are recommended without performance caveats. The common_mistakes list reinforces multiple non-obvious gotchas: VARCHAR storage, auto-increment enumeration, and ULID sorting issues.

About DEBT scoring →

Also Known As

primary key strategies ID generation sortable unique identifiers distributed ID systems

TL;DR

Primary key strategies: auto-increment is simple and fast, UUID v4 is globally unique but random (poor index performance), UUID v7 and ULID are sortable globally unique IDs.

Explanation

Auto-increment (BIGINT): simple, compact, sequential — poor for distributed systems (coordination needed). UUID v4: globally unique, no coordination, but fully random — causes B-tree index fragmentation and poor cache locality. UUID v7 (2023): UUID format with millisecond timestamp prefix — sortable, globally unique, good index performance. ULID: 26-char Base32, timestamp-sortable, URL-safe. For most PHP apps: BIGINT auto-increment for internal tables, UUID v7 or ULID for externally visible IDs (APIs, URLs). MySQL uses utf8mb4 for UUID storage; PostgreSQL has a native uuid type.

Common Misconception

✗ UUID v4 is the best choice for globally unique IDs — UUID v4's randomness causes B-tree index fragmentation and poor INSERT performance at scale; UUID v7 or ULID are better alternatives.

Why It Matters

Switching from auto-increment to UUID v4 on a high-write table can reduce INSERT throughput by 50%+ due to index fragmentation; UUID v7 gives global uniqueness without the performance penalty.

Common Mistakes

  • UUID v4 as primary key on high-write tables — random inserts fragment the B-tree index.
  • Storing UUIDs as VARCHAR(36) instead of BINARY(16) or native uuid type — 3× the storage and slower indexes.
  • Exposing auto-increment IDs in URLs — reveals business data (order count, user count) and enables enumeration.
  • ULIDs not sorted correctly in PHP — use a ULID library that returns lexicographically sortable strings.

Avoid When

  • When you need cryptographic security or unpredictability — use a proper random number generator or cryptographic hash instead.
  • When storage space is severely constrained and you cannot afford UUID's 16-byte footprint — auto-increment BIGINT is the only viable option.
  • When you're building a single-server monolith with no distributed system plans — auto-increment's simplicity and performance dominates.
  • When your primary concern is human readability or customer-facing sequential order numbers — these strategies are implementation details, not user-facing identifiers.

When To Use

  • Use auto-increment BIGINT for internal database tables where ordering is simple, coordination is local, and you need maximum query speed.
  • Use UUID v7 or ULID for user-facing IDs in APIs and URLs to avoid exposing sequential patterns and to support distributed services without central coordination.
  • Use UUID v7 over ULID when you need native database type support (PostgreSQL uuid) and strict RFC 4122 compliance.
  • Use ULID over UUID v7 when you prioritize human readability, URL-safe encoding, or need to parse timestamps directly from the ID without database queries.

Code Examples

💡 Note
Switching from `UUID()` (v4, random) to `Uuid::uuid7()` (timestamp-prefixed) as the PRIMARY KEY eliminates B-tree fragmentation because v7 values are monotonically increasing rather than scattered randomly across the key space.
✗ Vulnerable
-- UUID v4 as PK — random, index-fragmenting:
CREATE TABLE orders (
    id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),  -- Random, large, slow
    total DECIMAL(10,2)
);
-- 1M inserts: B-tree constantly reorganised, cache misses everywhere
✓ Fixed
-- UUID v7 (sortable) or ULID:
CREATE TABLE orders (
    id BINARY(16) PRIMARY KEY,  -- UUID v7: timestamp-prefixed, efficient
    total DECIMAL(10,2)
);

-- PHP with ramsey/uuid:
use Ramsey\Uuid\Uuid;
$id = Uuid::uuid7()->toString(); // 01956b3c-...: sortable, globally unique

-- Or ULID:
$id = (new Ulid())->toRfc4122(); // 01J3K2... — timestamp sortable

Added 15 Mar 2026
Edited 25 Sep 2026
Views 161
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings M 0 pings T 0 pings W 1 ping T 0 pings F 3 pings S 2 pings S 1 ping M 2 pings T 0 pings W 1 ping T 0 pings F 1 ping S 0 pings S 1 ping M 1 ping T 0 pings W 2 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 0 pings S 0 pings M 0 pings T
No pings yet today
No pings yesterday
Amazonbot 15 SEMrush 12 PetalBot 12 Google 9 Ahrefs 7 Perplexity 5 Bing 5 Scrapy 5 Brave Search 3 ChatGPT 3 Unknown AI 2 Claude 2 Twitter/X 2 Applebot 2 Majestic 1
crawler 85
🧱 FUNDAMENTALS — new to this? Start with the ground floor.
Database database A database is an organized collection of data stored electronically, designed so programs can efficiently retrieve, add, update, and delete information.

Nearly every application needs to remember information between sessions. Databases provide the reliable, fast, and organized storage that makes persistent data possible at any scale.

💡 Always use prepared statements with placeholders—never concatenate user input directly into database queries.

Ask Codex about Database →
⚡ DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Medium
⚡ Quick Fix
Use UUIDv7 (time-ordered) or ULID instead of UUIDv4 for primary keys — random UUIDs fragment B-tree indexes causing write performance degradation on large tables
📦 Applies To
PHP 7.0+ web cli queue-worker laravel
🔗 Prerequisites
🔍 Detection Hints
UUIDv4 as primary key on high-write table causing InnoDB index fragmentation; auto-increment exposing record count via URL
Auto-detectable: ✗ No percona-toolkit mysql-slow-query-log
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Low ✗ Manual fix Fix: Medium Context: File Tests: Update


✓ schema.org compliant