How Storage Engines Affect Primary Key Performance: MySQL vs PostgreSQL

A simple experiment comparing how MySQL and PostgreSQL handle random vs sequential IDs across 1 million rows.

Sai Srinivas
Sai Srinivas
Aug 24, 20265 min read
DatabasesMySQLPostgreSQL

When picking an ID for database rows, developers often debate between auto-incrementing numbers, random IDs (UUIDv4), or time-based IDs (UUIDv7).

Most people think the bottleneck is just the ID format. But the real difference comes down to how your database actually stores data on disk.

I ran a simple experiment comparing MySQL and PostgreSQL to see how their storage engines react when inserting random vs sequential IDs into a table with 1 million rows.


Table of Contents


1. The Big Difference: How They Store Data

MySQL and PostgreSQL store table data in two completely different ways.

MySQL (InnoDB): Index-Organized Table

In MySQL, the table is the primary key index.

The actual row data lives directly inside the leaf nodes of the B+Tree:

Root & Branch Nodes
        ↓
Leaf Nodes
        ↓
[ ID + All Other Column Data ]

Because row data is stored right inside the index, inserting a random ID forces MySQL to find a specific spot somewhere in the tree. If that page is full, MySQL has to split the page and move existing data around.

PostgreSQL: Heap Storage + Separate Index

PostgreSQL takes a different approach. It puts row data in a simple file called a heap, and keeps indexes separate:

Primary Key Index (B+Tree)
  └── Leaf Node: UUID Key ──> Pointer to Heap
                                    │
                                    ↓
Heap File (Table Pages)
  └── Row Data: [ All Column Data ]

The index only holds the ID and a small pointer to where the row lives in the heap.


2. Random vs. Sequential IDs

To test this, we used two types of IDs:

  1. UUIDv4 (Random): Lands in random spots across the index tree:
UUIDv4 (Random): 8f3c... -> 2a1b... -> c79e... -> 19df...
  1. UUIDv7 (Time-Ordered): Starts with a timestamp, so new IDs always go to the end of the tree in order:
UUIDv7 (Time-Ordered): 2026-08...A -> 2026-08...B -> 2026-08...C

3. The Experiment Setup

The setup was kept simple and identical for both databases:

The table structure:

id (UUID) | username (TEXT) | name (TEXT) | age (INT)

4. Results

Here are the numbers after inserting 10,000 rows into a 1M-row table:

MySQL / InnoDB

ID Type Average Time Median Total Time
UUIDv4 (Random) 0.7688 ms 0.6813 ms 7.71 s
UUIDv7 (Ordered) 0.6378 ms 0.5980 ms 6.39 s

In MySQL, random UUIDv4 was 20.5% slower than ordered UUIDv7.

PostgreSQL

ID Type Average Time Median Total Time
UUIDv4 (Random) 0.3937 ms 0.3685 ms 3.94 s
UUIDv7 (Ordered) 0.3874 ms 0.3617 ms 3.88 s

In PostgreSQL, random UUIDv4 was only 1.63% slower than ordered UUIDv7.

Side-by-Side Comparison

ID Type MySQL / InnoDB PostgreSQL
UUIDv4 (Random) 0.7688 ms 0.3937 ms
UUIDv7 (Ordered) 0.6378 ms 0.3874 ms
Difference +20.5% slower +1.63% slower

5. The Trade-off: Reads vs. Writes

Why did MySQL slow down by 20% while PostgreSQL barely noticed?

Neither design is "better" or "worse". They just make different trade-offs:

If you are generating distributed IDs on a clustered B+Tree database like MySQL/InnoDB, switching from UUIDv4 to time-ordered UUIDv7 gives you the best of both worlds: global uniqueness with fast sequential write throughput.


6. References

  1. Database Internals: A Deep Dive into How Distributed Data Systems Work by Alex Petrov