Si estás usando SQLite en un producto real, tarde o temprano te vas a topar con esta decisión: ¿usar un UUID como clave primaria o no? En prototipos suele parecer una elección limpia, porque te evita pensar en secuencias, te permite generar IDs en cualquier cliente y suena bien cuando imaginas sincronización, offline-first o múltiples servicios escribiendo datos. El problema es que en SQLite esa comodidad puede salir cara más rápido de lo que parece.
No se trata de una guerra religiosa contra los UUID. Se trata de entender que SQLite no se comporta igual que una base de datos pensada para entornos distribuidos con mucho tráfico concurrente. Si tu app guarda pedidos, eventos, usuarios o contenidos y necesita responder rápido con un archivo pequeño, una clave primaria de 36 caracteres puede empujarte a un diseño más pesado de lo necesario.
Qué cambia cuando usas UUID en SQLite
SQLite guarda los datos en páginas y organiza muchas búsquedas a través de B-tree. Eso significa que el tamaño de la clave importa, porque afecta cuántos registros caben en cada página, cuánta memoria se mueve y cuánto trabajo hace el motor para insertar, buscar y mantener los índices. Un UUID textual típico ocupa 36 bytes solo en la representación con guiones, sin contar sobrecarga interna. Un entero de 64 bits ocupa 8 bytes. La diferencia no es marginal.
Si tu tabla tiene una primary key basada en UUID y además otros índices secundarios, ese costo se multiplica. Cada índice tiene que guardar la referencia a la fila, y si esa referencia es larga, el índice crece. Más tamaño implica más páginas, más I/O y más probabilidad de que el working set no quepa en caché. En una app de escritorio o móvil eso se nota más de lo que muchos equipos esperan.
La documentación oficial de SQLite explica que los índices son estructuras B-tree y que el tamaño de las claves afecta directamente su eficiencia. Puedes revisarlo en la documentación de índices de SQLite y en las notas sobre WITHOUT ROWID tables, que son relevantes cuando comparas estrategias de clave primaria.
El costo no es solo almacenamiento
El error común es pensar que el problema del UUID es únicamente que “ocupa más”. En realidad, también afecta la localización física de los datos. Los UUID aleatorios insertan filas en posiciones menos predecibles dentro del árbol, lo que puede causar más page splits y más fragmentación lógica. Con una clave incremental, la inserción suele avanzar de forma más ordenada.
En una tabla pequeña no vas a notar mucho. Pero cuando pasas a cientos de miles o millones de filas, el patrón se vuelve visible. Un equipo que mide solo la latencia de una consulta puntual puede perder de vista que el verdadero costo está en la suma: inserts más lentos, índices más grandes, backups más pesados y más tiempo de sincronización.
También hay un efecto en la depuración. Un entero autoincremental te deja ver rápidamente el orden de creación. Un UUID no te da esa pista. Si el sistema depende de trazabilidad temporal, terminas agregando otra columna para created_at, otro índice y otro punto de mantenimiento.
Cuándo un UUID sí tiene sentido
No todo es contra los UUID. Hay casos donde sí aportan valor real. Si generas datos en varios dispositivos sin conexión, si fusionas bases locales con un backend central o si necesitas IDs globalmente únicos sin coordinar un contador, el UUID resuelve un problema práctico. En productos con sincronización offline, por ejemplo, evita colisiones entre clientes.
También puede servir cuando el ID se expone fuera del sistema y no quieres revelar volumen de registros ni orden de creación. Pero incluso ahí conviene separar el identificador público del identificador interno. Puedes tener un UUID público y una clave interna más compacta para la tabla principal.
La pregunta correcta no es “¿UUID sí o no?”, sino “¿UUID como primary key interna o como identificador externo?”. Esa distinción cambia mucho el diseño. En muchos productos, la mejor respuesta es usar ambos: un entero o rowid como clave interna y un UUID único para integración, URLs o APIs.
Casos donde el UUID encaja mejor
- Sincronización offline entre varios clientes que crean registros sin conexión.
- Integraciones donde varios sistemas generan datos y luego los consolidan.
- APIs públicas donde no quieres exponer secuencias predecibles.
- Migraciones desde sistemas distribuidos que ya usan UUID como estándar.
Si tu caso no cae en una de esas categorías, probablemente estás pagando complejidad sin obtener suficiente beneficio. Y en SQLite, donde la simplicidad suele ser una ventaja competitiva, esa cuenta pesa más.
Qué pasa en el rendimiento real
El impacto concreto depende de tu carga. Si haces pocas escrituras y muchas lecturas por clave primaria, quizá el efecto sea tolerable. Si tu app escribe seguido, sincroniza lotes o mantiene varias tablas con índices, el costo se acumula. En SQLite esto importa porque suele usarse precisamente en escenarios con recursos limitados: apps móviles, escritorios, edge, herramientas internas y productos embebidos.
Un UUID como texto también complica el ancho de banda interno de la base. Si lo guardas como TEXT, el motor maneja cadenas de 36 caracteres. Si lo guardas como BLOB, mejoras bastante el tamaño, pero sigues teniendo una clave de 16 bytes frente a 8 bytes de un entero. Además, la comparación y el ordenamiento siguen siendo menos amigables que con un entero secuencial.
La diferencia se nota en tres puntos muy concretos:
- El archivo de la base crece más rápido.
- Los índices secundarios también crecen.
- Las escrituras aleatorias generan más trabajo de mantenimiento del árbol.
No hace falta dramatizarlo. Basta con medirlo. Un experimento simple con una tabla de millones de filas, comparando INTEGER PRIMARY KEY frente a UUID en TEXT o BLOB, suele mostrar diferencias claras en tamaño total y tiempo de inserción. La magnitud exacta depende del hardware y del patrón de acceso, así que no conviene inventar porcentajes universales.
TEXT, BLOB o INTEGER
Si vas a usar UUID, el formato importa. TEXT es el más cómodo para leer y depurar, pero también el más caro en almacenamiento. BLOB reduce bastante el tamaño, aunque exige convertir ida y vuelta en tu aplicación. INTEGER PRIMARY KEY sigue siendo la opción más eficiente para SQLite cuando no necesitas unicidad distribuida.
Aquí tienes una comparación práctica:
| Tipo de clave | Tamaño típico | Legibilidad | Costo en índices | Mejor uso |
|---|---|---|---|---|
INTEGER PRIMARY KEY | 8 bytes | Alta | Bajo | Tablas internas, alto volumen |
UUID como TEXT | 36 bytes | Alta | Alto | Debug, APIs, prototipos |
UUID como BLOB | 16 bytes | Baja | Medio | Sincronización, menor huella |
Si tu equipo está empezando, muchas veces el mejor camino es usar INTEGER PRIMARY KEY para la tabla principal y agregar una columna public_id con UUID único cuando realmente lo necesitas. Así separas el problema de almacenamiento del problema de identificación externa.
Cómo diseñarlo sin complicarte
La forma más sana de evitar dolores de cabeza es no convertir el UUID en la columna que todo lo demás depende. En otras palabras, no hagas que cada relación, índice y join cargue con el peso del identificador largo si no hace falta. SQLite funciona muy bien cuando le das claves pequeñas y patrones de acceso simples.
Un patrón razonable es este: la tabla usa un INTEGER PRIMARY KEY, y el UUID vive como UNIQUE en una columna aparte. Eso te permite exponer un identificador estable al exterior sin sacrificar demasiada eficiencia interna. Si luego migras a otra base o integras servicios, el UUID sigue ahí.
Ejemplo básico:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
public_id BLOB NOT NULL UNIQUE,
email TEXT NOT NULL UNIQUE,
created_at TEXT NOT NULL
);
Ese diseño no es universal, pero sí práctico. public_id puede ser un UUID binario de 16 bytes, y id queda como clave interna compacta. Si tu app necesita mostrar un ID externo en URLs o logs, usas public_id. Si necesita joins eficientes, usas id.
Estrategia recomendada para equipos pequeños
- Usa
INTEGER PRIMARY KEYen tablas internas de alto volumen. - Agrega UUID solo cuando haya una razón clara de negocio o sincronización.
- Guarda el UUID como
BLOBsi tu stack lo soporta bien. - Mantén índices secundarios al mínimo necesario.
- Mide tamaño de archivo y tiempo de inserción antes de decidir.
Si ya tienes una base con UUID como primary key, no hace falta reescribir todo de golpe. Puedes evaluar una migración gradual, crear una nueva tabla con clave compacta y mover tráfico por etapas. Lo importante es no seguir escalando un diseño que ya sabes que te está costando.
Cuándo sí conviene pagar el costo
Hay escenarios donde el UUID como primary key sigue siendo una decisión válida. Si tu prioridad es la generación descentralizada de IDs y el costo extra no te afecta, adelante. También si tu volumen es bajo y el producto prioriza simplicidad de integración sobre rendimiento, la penalización puede ser aceptable.
Pero conviene ser honesto con el contexto. No es lo mismo una app interna con 20 mil filas que un sistema de tickets con millones de eventos y sincronización frecuente. En el primer caso, el UUID puede pasar desapercibido. En el segundo, puede convertirse en una fuente continua de fricción.
La clave está en no tomar la decisión por inercia. Muchas veces se adopta UUID porque se ve moderno, porque otro servicio ya lo usa o porque alguien lo recomienda sin mirar el motor de base de datos. Con SQLite, esa decisión merece una revisión más fría.
Si quieres una referencia directa sobre cómo SQLite organiza tablas y claves, también vale la pena leer la explicación oficial de rowid tables. Ahí se entiende por qué el rowid es tan eficiente y por qué renunciar a él tiene consecuencias reales.
Tabla resumen
| Pregunta | Respuesta corta |
|---|---|
| ¿UUID siempre es mala idea? | No, pero en SQLite suele costar más que un entero. |
| ¿Cuál es el problema principal? | Tamaño de clave, índices más grandes y más trabajo en escrituras. |
¿UUID como TEXT o BLOB? | BLOB suele ser mejor si de verdad necesitas UUID. |
| ¿Qué conviene para tablas internas? | INTEGER PRIMARY KEY. |
| ¿Cuándo usar UUID? | Cuando necesitas unicidad distribuida o IDs públicos. |
| ¿Se puede combinar ambos? | Sí, y muchas veces es la mejor opción. |
La decisión no debería salir de una preferencia estética, sino de tu carga real, tu volumen de datos y el tipo de producto que estás construyendo. Si SQLite es parte central de tu app, cada byte en la clave primaria se multiplica con el tiempo.
Preguntas frecuentes
¿UUID en SQLite siempre empeora el rendimiento?
¿Es mejor guardar el UUID como TEXT o como BLOB?
¿Puedo usar UUID como clave primaria y todavía ir bien?
¿Cuál es la alternativa más práctica en SQLite?
¿UUID sirve para sincronización offline?
¿Debo migrar si ya tengo UUID como primary key?
¿SQLite soporta bien claves primarias compuestas con UUID?
Azirgo
¿Listo para construir tu Producto Digital?
Sitios web, apps móviles, software a medida y soluciones blockchain. Cuéntanos qué tienes en mente y armamos un plan claro contigo.
- Cotización clara en 48 horas
- Equipo en Ecuador, atención en español
- Desde un MVP hasta un producto en producción