Los problemas de usar UUID aleatorios como clave primaria en SQLite

Fuentes: The perils of UUID primary keys in SQLite
Imagen generada por IA con el prompt: Abstract 3D rendering of a B-tree index structure with red and blue nodes representing performance overhead, dark gradient background, technical editorial illustration, glowing accents
Imagen generada con IA

Las bases de datos recurren con frecuencia a UUID aleatorios como clave primaria por su unicidad distribuida, pero esta elección tiene un coste de rendimiento que muchos desarrolladores desconocen. En SQLite, cada tabla ordinaria dispone de un rowid implícito de 64 bits que actúa como índice agrupado, de modo que el orden físico de los datos en disco sigue la secuencia de inserción. Al declarar una tabla como WITHOUT ROWID, la clave primaria designada se convierte en el índice agrupado y las filas se almacenan ordenadas por esa clave.

El problema aparece con UUID4, cuyo carácter aleatorio hace que cada nueva fila caiga en una posición impredecible del B-tree. En lugar de añadir registros al final, el motor debe partir páginas existentes, reequilibrar el árbol y reescribir datos constantemente, multiplicando las operaciones de E/S. Anders Murphy lo demuestra con un benchmark: insertar lotes de un millón de filas tarda entre 1,07 y 1,21 segundos con una clave entera autoincremental, pero con UUID4 el primer millón necesita 2,6 segundos y el décimo millón se dispara hasta 12,5 segundos, es decir, entre 10 y 12 veces más.

El autor complementó la prueba con un diffgraph generado mediante clj-async-profiler. La visualización muestra que el tiempo extra se concentra en las funciones de equilibrado, lectura y escritura del B-tree, justo lo esperado por la naturaleza desordenada de UUID4. La degradación es progresiva: cuanto más crece la tabla, peor es el rendimiento, porque el coste de mantener el árbol se acumula.

La solución propuesta son los UUID7, una variante ordenada por timestamp en sus primeros bits. Al mantener un orden monótono creciente, las inserciones se comportan como una clave entera, evitando casi todo el reequilibrio. En el mismo benchmark, UUID7 registra entre 1,25 y 1,37 segundos por millón de filas, apenas un 15% por encima de la línea base. La diferencia residual se explica por el tamaño: un UUID ocupa 16 bytes frente a los 8 de un entero de 64 bits.

Aunque centrado en SQLite, el artículo recuerda que el problema se extiende a cualquier base de datos con índices agrupados, como SQL Server o MySQL InnoDB. La recomendación práctica es utilizar UUID7 o un esquema híbrido con entero autoincremental como clave primaria y UUID como columna secundaria única. Murphy publica el código del benchmark en su repositorio de GitHub para reproducir los resultados.