Skip to content

CONCEPT

UUID string representation in MySQL

MySQL has no native UUID column type; UUIDs in MySQL are stored as 36-character strings (VARCHAR(36)) or as 16-byte BINARY(16) with application-side formatting. Postgres has a native 16-byte UUID column. The asymmetry affects storage footprint, primary-key behaviour, and migration plans.

The UUID data type is essentially a 36 character long string. So we can set this data type to a varchar(36) in MySQL. MySQL also supports generating UUID's via a UUID() function, so when it comes to adding new records, we could utilize the UUID() function that behaves similarly to Postgres's gen_random_uuid() function.

Generation

Postgres MySQL
gen_random_uuid() UUID()
Returns UUID type Returns VARCHAR text

Storage choices in MySQL

  • VARCHAR(36) — human-readable, easy to debug, 36 bytes per key. The path the PlanetScale post recommends as the drop-in.
  • BINARY(16) — compact, 16 bytes per key. Requires application-side or helper-function conversion (UUID_TO_BIN, BIN_TO_UUID in MySQL 8.0+). Matches the storage footprint of Postgres's native UUID.
  • CHAR(36) — fixed-width, equivalent in practice to VARCHAR(36) for UUIDs.

Interaction with clustered-index primary keys

InnoDB organises tables as B+trees keyed by the primary key. Using a random UUID as a VARCHAR(36) primary key triggers the full concepts/uuid-primary-key-antipattern — random insert points, write amplification, and non-sequential scans — and amplifies it further by ~2.25× the key bytes compared to BINARY(16).

Migration plans that move Postgres UUID-keyed tables to MySQL have two good exits:

  1. Keep UUIDs, switch to BINARY(16) — preserves the opaque-identifier contract, minimises the storage penalty.
  2. Move off UUIDs in the migration window — introduce a BIGINT AUTO_INCREMENT internal key, keep the UUID as a secondary unique index for external contract. See patterns/sequential-primary-key.

Seen in

  • — Brian Morrison II (PlanetScale, 2024-03-19) canonicalises the byte-wise storage ratios: CHAR(36) = 288 bits = 9× a 32-bit INT; BINARY(16) = 128 bits = 4× INT. Adds the MySQL 8.0+ UUID_TO_BIN / BIN_TO_UUID helper functions (with the optional swap flag — see concepts/uuid-to-bin-swap-flag) as the canonical path for converting between representations, with a worked SQL example.
  • — Adnan Kukic (PlanetScale, 2023-02-09) canonicalises VARCHAR(36) as the drop-in mapping for Postgres's native UUID type during cross-engine migrations, with the UUID() generator function as equivalent of gen_random_uuid().
Last updated · 542 distilled / 1,571 read