UUID como clave primaria
Los UUIDs (Universally Unique Identifiers) son identificadores de 128 bits diseñados para ser únicos en todo el mundo sin necesidad de coordinación central. Usar UUIDs como claves primarias en lugar de enteros auto-incrementales tiene ventajas significativas en sistemas distribuidos, pero también tiene implicaciones importantes en el rendimiento de MySQL que debes conocer antes de adoptarlos.
Generar UUIDs en MySQL
MySQL incluye la función UUID() que genera identificadores en formato estándar:
SELECT UUID();| UUID() |
|---|
| 550e8400-e29b-41d4-a716-446655440000 |
El resultado es una cadena de 36 caracteres con el formato xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. Desde MySQL 8.0, también está disponible UUID_TO_BIN() y BIN_TO_UUID() para almacenar UUIDs de forma eficiente como BINARY(16):
SELECT UUID_TO_BIN(UUID());
SELECT BIN_TO_UUID(UUID_TO_BIN('550e8400-e29b-41d4-a716-446655440000'));Crear una tabla con UUID como clave primaria
La forma más eficiente de almacenar UUIDs es como BINARY(16), no como VARCHAR(36):
CREATE TABLE pedidos_uuid (
id BINARY(16) NOT NULL DEFAULT (UUID_TO_BIN(UUID(), TRUE)),
cliente_id INT NOT NULL,
total DECIMAL(10,2) NOT NULL,
fecha_pedido DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB;El segundo argumento TRUE en UUID_TO_BIN(UUID(), TRUE) es crucial: reordena los bytes del UUID para que las versiones basadas en tiempo (UUID v1) sean secuenciales, lo que mejora drásticamente el rendimiento del índice.
-- Insertar un registro
INSERT INTO pedidos_uuid (cliente_id, total) VALUES (1, 299.99);
-- Consultar mostrando el UUID legible
SELECT BIN_TO_UUID(id, TRUE) AS id, cliente_id, total, fecha_pedido
FROM pedidos_uuid;| id | cliente_id | total | fecha_pedido |
|---|---|---|---|
| 018d9e3a-5b2f-7c1e-a8b4-2f3c4d5e6f7a | 1 | 299.99 | 2026-02-14 15:30:00 |
Ventajas de UUID como clave primaria
Los UUIDs pueden generarse en cualquier lugar sin consultar la base de datos. Un servidor de aplicación, un dispositivo móvil o un servicio en la nube pueden generar un ID válido antes de insertar el registro. Esto elimina la dependencia del AUTO_INCREMENT que requiere una conexión a la base de datos.
En sistemas distribuidos con múltiples servidores de escritura, los UUIDs evitan conflictos de IDs. Con AUTO_INCREMENT, dos servidores podrían generar el mismo ID simultáneamente a menos que configures offsets y incrementos personalizados.
Los UUIDs son impredecibles, lo que puede ser una ventaja de seguridad. Un usuario no puede adivinar los IDs de otros registros como sí podría con enteros secuenciales.
Desventajas y el problema del rendimiento
Los UUIDs aleatorios (v4) causan un problema severo de rendimiento con los índices B-tree de InnoDB. Como los valores son aleatorios, cada inserción puede ir a cualquier posición del índice, lo que provoca constantes divisiones de páginas y fragmentación. En tablas con millones de filas, las inserciones pueden ser entre 2x y 10x más lentas que con enteros secuenciales.
Los UUIDs como BINARY(16) ocupan 16 bytes frente a los 4 bytes de un INT o los 8 de un BIGINT. Esto aumenta el tamaño de los índices, reduce la cantidad de registros que caben en memoria, y ralentiza los JOINs porque las comparaciones de 16 bytes son más lentas que las de 4 bytes.
La legibilidad también se ve afectada. Depurar consultas con UUIDs binarios es menos intuitivo que con enteros simples.
La solución: UUID_TO_BIN con swap_flag
MySQL 8.0 introdujo la función UUID_TO_BIN() con un segundo parámetro que reordena los bytes del UUID para hacerlo secuencial. Esto mitiga significativamente el problema de rendimiento:
-- Sin reordenamiento (aleatorio, malo para índices)
SELECT HEX(UUID_TO_BIN(UUID()));
-- Con reordenamiento (secuencial, bueno para índices)
SELECT HEX(UUID_TO_BIN(UUID(), TRUE));El flag TRUE mueve los bytes de timestamp al inicio del UUID, haciendo que los UUIDs generados secuencialmente se almacenen de forma secuencial en el índice, similar a un AUTO_INCREMENT.
Comparación de rendimiento
Para una tabla con 1 millón de inserciones:
| Tipo de clave | Tiempo de inserción | Tamaño del índice |
|---|---|---|
| INT AUTO_INCREMENT | ~15 seg | ~16 MB |
| BINARY(16) UUID aleatorio | ~45 seg | ~48 MB |
| BINARY(16) UUID ordenado | ~18 seg | ~48 MB |
| VARCHAR(36) UUID | ~60 seg | ~85 MB |
El UUID ordenado (con swap_flag) se acerca al rendimiento del entero auto-incremental en inserciones, aunque el índice sigue siendo más grande por el tamaño del tipo de dato.
Patrón recomendado
Si decides usar UUIDs, este es el patrón recomendado para MySQL 8.0+:
CREATE TABLE entidad (
id BINARY(16) NOT NULL DEFAULT (UUID_TO_BIN(UUID(), TRUE)),
-- otras columnas...
PRIMARY KEY (id)
) ENGINE=InnoDB;
-- Insertar sin especificar el ID (se genera automáticamente)
INSERT INTO entidad (columna1, columna2) VALUES ('valor1', 'valor2');
-- Buscar por UUID legible
SELECT * FROM entidad
WHERE id = UUID_TO_BIN('018d9e3a-5b2f-7c1e-a8b4-2f3c4d5e6f7a', TRUE);Funciones auxiliares
Puedes crear funciones para simplificar el trabajo con UUIDs:
-- Función para generar UUID ordenado
CREATE FUNCTION uuid_ordenado()
RETURNS BINARY(16)
DETERMINISTIC
RETURN UUID_TO_BIN(UUID(), TRUE);
-- Función para mostrar UUID legible
CREATE FUNCTION uuid_legible(b BINARY(16))
RETURNS VARCHAR(36)
DETERMINISTIC
RETURN BIN_TO_UUID(b, TRUE);Cuándo usar UUID vs AUTO_INCREMENT
Usa UUID cuando necesites generar IDs fuera de la base de datos, cuando tengas múltiples servidores de escritura, cuando la seguridad por oscuridad de los IDs sea importante, o cuando necesites combinar datos de múltiples fuentes sin conflictos de IDs.
Usa AUTO_INCREMENT cuando el rendimiento máximo de inserciones sea crítico, cuando las tablas tengan muchos índices secundarios (cada uno incluye la clave primaria), cuando la simplicidad y legibilidad sean prioritarias, o cuando trabajes con un solo servidor de escritura.
En muchos casos, la mejor solución es usar AUTO_INCREMENT como clave primaria para el rendimiento del motor y añadir una columna UUID como clave de negocio para la exposición externa:
CREATE TABLE pedidos (
id INT AUTO_INCREMENT PRIMARY KEY,
uuid BINARY(16) NOT NULL DEFAULT (UUID_TO_BIN(UUID(), TRUE)),
cliente_id INT NOT NULL,
total DECIMAL(10,2),
UNIQUE INDEX uk_uuid (uuid)
);Esta combinación te da el rendimiento óptimo de un entero como clave primaria y la universalidad de un UUID como identificador público.
Escrito por Eduardo Lázaro
