UUID vs Auto-Increment: Quale Scegliere per il Database?
UUID vs Auto-Increment: confronto completo per database. Vantaggi, svantaggi, performance e criteri per scegliere l'identificatore giusto per il tuo caso.
Il Dilemma delle Primary Key
Scegliere tra UUID e auto-increment per le chiavi primarie è una decisione architetturale importante che influenza performance, scalabilità e sicurezza dell'applicazione. Non esiste una risposta universale: ogni approccio ha vantaggi specifici per casi d'uso diversi. Questa guida analizza entrambe le opzioni per aiutarti a scegliere consapevolmente.
Auto-Increment (Sequential ID)
Come Funziona
-- MySQL/MariaDB
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL
);
-- PostgreSQL
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL
);
-- oppure con IDENTITY (SQL standard)
CREATE TABLE users (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email VARCHAR(255) NOT NULL
);
-- Inserimento
INSERT INTO users (email) VALUES ('mario@example.com');
-- id = 1
INSERT INTO users (email) VALUES ('luigi@example.com');
-- id = 2
Vantaggi
- Dimensione ridotta: INT = 4 byte, BIGINT = 8 byte
- Performance indice: Inserimenti sequenziali ottimali per B-tree
- Leggibilità: ID 42 è più facile da ricordare di un UUID
- Debug semplice: Ordine cronologico implicito
- Join veloci: Meno byte da confrontare
Svantaggi
- Prevedibilità: /users/1, /users/2... enumerable
- Single point of failure: Il database genera l'ID
- Merge difficile: Conflitti tra database/shard
- Limite: INT max = 2 miliardi (BIGINT = 9 quintilioni)
- Esposizione dati: Rivela numero di record
UUID (Universally Unique Identifier)
Come Funziona
-- PostgreSQL (supporto nativo)
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL
);
-- MySQL 8.0+
CREATE TABLE users (
id BINARY(16) PRIMARY KEY,
email VARCHAR(255) NOT NULL
);
-- Oppure come stringa (meno efficiente)
CREATE TABLE users (
id CHAR(36) PRIMARY KEY,
email VARCHAR(255) NOT NULL
);
-- Inserimento
INSERT INTO users (id, email)
VALUES (UUID(), 'mario@example.com');
-- id = '550e8400-e29b-41d4-a716-446655440000'
Versioni UUID
# UUIDv1: Timestamp + MAC address
# Pro: Ordinabile per tempo
# Contro: Espone MAC address, privacy
# Esempio: 6ba7b810-9dad-11d1-80b4-00c04fd430c8
# UUIDv4: Completamente random
# Pro: Sicuro, imprevedibile
# Contro: Frammentazione indice
# Esempio: f47ac10b-58cc-4372-a567-0e02b2c3d479
# UUIDv7: Timestamp + random (draft 2024)
# Pro: Ordinabile + sicuro, best of both worlds
# Contro: Nuovo, supporto limitato
# Esempio: 018e5f9c-7c00-7000-8000-000000000001
Vantaggi
- Unicità globale: Generabile ovunque senza coordinazione
- Sicurezza: Non enumerable, non rivela conteggio
- Distribuito: Perfetto per microservizi e sharding
- Merge semplice: No conflitti tra database
- Client-side: Generabile prima dell'insert
Svantaggi
- Dimensione: 16 byte (vs 4-8 per INT)
- Performance: Random causa frammentazione B-tree
- Leggibilità: Difficili da ricordare/comunicare
- URL lunghi: /users/550e8400-e29b-41d4-a716-446655440000
Confronto Performance
Benchmark Insert
# Test: 1 milione di insert
# Auto-increment INT
Tempo: 45 secondi
Dimensione indice: 12 MB
# UUID v4 (BINARY 16)
Tempo: 120 secondi
Dimensione indice: 48 MB
# UUID v4 (CHAR 36)
Tempo: 180 secondi
Dimensione indice: 108 MB
# UUID v7 (BINARY 16)
Tempo: 55 secondi # Molto meglio!
Dimensione indice: 48 MB
Perché UUIDv4 è Lento
# B-tree index con inserimenti random
# Auto-increment: inserisce sempre alla fine
[1] [2] [3] [4] [5] [6] [7] [8]
↑ nuovo
# UUIDv4: inserisce in posizione random
[abc...] [def...] [ghi...] [jkl...]
↑ nuovo qui! (causa page split)
# Page split = riscrittura pagine = lento
# Soluzione: UUIDv7 (timestamp prefix)
# Inserisce quasi sequenzialmente come auto-increment
Pattern Ibridi
ID Interno + UUID Pubblico
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
public_id UUID UNIQUE DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL
);
-- Join interni veloci con id
SELECT * FROM orders WHERE user_id = 42;
-- API usa public_id
GET /api/users/550e8400-e29b-41d4-a716-446655440000
-- Index su public_id per lookup API
CREATE INDEX idx_users_public_id ON users(public_id);
ULID (Universally Unique Lexicographically Sortable Identifier)
# ULID: alternativa a UUID
# 26 caratteri, Crockford Base32
# Timestamp + random, ordinabile
# Formato: TTTTTTTTTTRRRRRRRRRRRRR
# T = timestamp (ms), R = random
# Esempio
01ARZ3NDEKTSV4RRFFQ69G5FAV
# Vantaggi vs UUID:
# - Più corto (26 vs 36 caratteri)
# - Case-insensitive
# - Ordinabile cronologicamente
# - No caratteri speciali
# JavaScript
import { ulid } from 'ulid';
const id = ulid(); // 01ARZ3NDEKTSV4RRFFQ69G5FAV
Snowflake ID (Twitter)
# 64-bit ID, ordinabile, distribuito
# Formato: timestamp | datacenter | worker | sequence
# Pro:
# - Dimensione INT (8 byte)
# - Ordinabile per tempo
# - Supporta 4096 worker
# Usato da: Twitter, Discord, Instagram
# JavaScript (esempio semplificato)
function snowflake() {
const epoch = 1288834974657n;
const timestamp = BigInt(Date.now()) - epoch;
const workerId = 1n;
const sequence = 0n;
return (timestamp << 22n) | (workerId << 12n) | sequence;
}
Quando Usare Cosa
Scegli Auto-Increment quando:
- Database singolo, non distribuito
- Performance è priorità massima
- Applicazioni interne, non pubbliche
- Volume dati < 2 miliardi record
- Non esponi ID nelle API pubbliche
Scegli UUID quando:
- Sistema distribuito o microservizi
- Merge frequenti tra database
- ID generati client-side
- Sicurezza: no enumerazione
- API pubbliche
Scegli UUIDv7/ULID quando:
- Vuoi vantaggi UUID + ordinabilità
- Performance è importante
- Nuovo progetto senza vincoli legacy
Implementazione
Node.js
// UUIDv4
import { randomUUID } from 'crypto';
const id = randomUUID();
// UUIDv7 (con libreria)
import { uuidv7 } from 'uuidv7';
const id = uuidv7();
// ULID
import { ulid } from 'ulid';
const id = ulid();
PostgreSQL
-- UUIDv4 (built-in)
SELECT gen_random_uuid();
-- UUIDv7 (con estensione pg_uuidv7)
CREATE EXTENSION pg_uuidv7;
SELECT uuid_generate_v7();
-- O con funzione custom
CREATE OR REPLACE FUNCTION uuid_v7()
RETURNS uuid AS $$
SELECT encode(
set_byte(
set_byte(
overlay(uuid_send(gen_random_uuid())
placing substring(int8send(
floor(extract(epoch from clock_timestamp()) * 1000)::bigint
) from 3) from 1 for 6),
6, (get_byte(uuid_send(gen_random_uuid()), 6) & 15) | 112
),
8, (get_byte(uuid_send(gen_random_uuid()), 8) & 63) | 128
),
'hex'
)::uuid;
$$ LANGUAGE sql VOLATILE;
Considerazioni Finali
La scelta tra UUID e auto-increment impatta anche la strategia di backup e replica. Gli auto-increment possono causare conflitti in scenari di multi-master replication. Gli UUID facilitano la merge di database separati senza remap degli ID. Considera anche l'impatto sui log e debug: gli ID sequenziali sono più facili da comunicare telefonicamente o in ticket di supporto rispetto agli UUID.
Strumenti Correlati
Per lavorare con identificatori:
- UUID Generator - Genera UUID v4, v7, ULID
- Hash Generator - Crea identificatori hash
- Base64 Encoder - Codifica ID per URL
Conclusione
La scelta dipende dal contesto:
- Monolith tradizionale: Auto-increment con INT/BIGINT
- API pubblica: UUID pubblico + ID interno
- Distribuito: UUIDv7 o ULID
- Alta scala: Snowflake ID
Nel 2025, UUIDv7 sta diventando lo standard de facto per nuovi progetti: combina unicità globale, ordinabilità e buone performance.
Per altri strumenti utili, esplora i nostri tool online gratuiti. Per approfondimenti, consulta UUID v7 draft RFC.