Pick the smallest type that holds every legitimate value, with room to grow. Narrow rows fit more per 16 KB InnoDB page and in memory, and the primary key matters most because every secondary index copies it (Clustered Indexes). Here 200,000 events go into habitual types and into narrow ones:
CREATE TABLE events_wide (id BIGINT AUTO_INCREMENT PRIMARY KEY, uuid CHAR(36) NOT NULL,
status VARCHAR(20) NOT NULL, at DATETIME(6) NOT NULL, KEY (uuid));
CREATE TABLE events_narrow (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
uuid BINARY(16) NOT NULL, status ENUM('pending','paid','shipped','cancelled') NOT NULL,
at DATETIME NOT NULL, KEY (uuid));
SET SESSION cte_max_recursion_depth = 300000;
INSERT INTO events_wide (uuid, status, at)
WITH RECURSIVE n (i) AS (SELECT 1 UNION ALL SELECT i + 1 FROM n WHERE i < 200000)
SELECT UUID(), ELT(1 + i % 4, 'pending', 'paid', 'shipped', 'cancelled'),
'2026-01-01' + INTERVAL i MINUTE FROM n;
INSERT INTO events_narrow (uuid, status, at)
SELECT UUID_TO_BIN(uuid), status, at FROM events_wide;
ANALYZE TABLE events_wide, events_narrow;
SELECT TABLE_NAME, TABLE_ROWS, ROUND(DATA_LENGTH / 1048576, 1) AS data_mb,
ROUND(INDEX_LENGTH / 1048576, 1) AS index_mb FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME LIKE 'events%';Output
... +---------------+------------+---------+----------+ | TABLE_NAME | TABLE_ROWS | data_mb | index_mb | +---------------+------------+---------+----------+ | events_narrow | 199826 | 9.5 | 5.5 | | events_wide | 199213 | 16.5 | 10.6 | +---------------+------------+---------+----------+
The same information takes 42% less data and 48% less index: the key shrank from 8 bytes to 4, the UUID from 36 to 16, the status to 1, and the time lost 3 bytes of microseconds. Leave room to grow, though: retyping a key later rewrites every referencing table.