SQL: ¿Por qué puede ser buena idea evitar el incremento automático?

Después de trabajar con diferentes proyectos que requerían bases de datos relacionales, me he encontrado con distintos problemas y reflexiones conceptuales que estoy seguro que se parecen a algunas de las tuyas.

Identificación de las entidades

Uno de estos problemas está directamente relacionado con la identificación (ID) de las entidades.

¿Debe el ID ser un valor número de incremento automático para cada entidad, o es mejor utilizar otro formato alternativo?

Es cierto que existe una alternativa, y muchos desarrolladores y proyectos con bases de datos relacionales están utilizando UUIDs (Identificadores universalmente únicos) como claves primarias. Hoy día, sobretodo porque nos comunicamos en APIs, los UUIDs están en todos los sitios y te permiten exponer datos sin revelar información sensible.

Además, al contrario de los identificadores numéricos incrementados automáticamente, los UUIDs no son previsibles, o al menos son muy difíciles de predecir, porque por ejemplo, no puedes predecir de forma fácil la URL de una entidad añadiendo secuencias de IDs, y pueden tener tener un rendimiento eficiente.

Además de limitar problemas relacionados con reglas CASCADE, si por ejemplo tienes alguna regla ON DELETE UPDATE con una clave externa (foreign key) cuyo DELETE se propaga en toda la base datos.

Entremos un poco más en detalle a este tema.

La clave primaria

Una base de datos relacional “es una colección de items de datos” con relaciones predefinidas entre ellos. Es, por tanto, un grafo en que los nodos se llaman entidades y las aristas son las relaciones. Para expresar una relación entre entidades necesitamos referir a una entidad de forma única, y es para ello que sirve la clave primaria.

Gracias a la clave primaria puedes tener una entidad estable y con referencia indexable.

Además, tu clave primaria puede ser una clave semántica o una clave técnica. Una clave primaria técnica no tiene ninguna relación con los campos de tu entidad mientras que una clave primaria semántica extrae de los atributos de las entidades. Para ello, las claves semánticas proporcionan una forma sencilla de comparar las entidades.

Lo que pasa es que, si usas bases de datos relacionales, sabes que las bases de datos relacionales se basan en un modelo mutable, o sea, las entidades están sujetas a mutaciones y los problemas empiezan a aparecer cuando la base de datos crece mucho a nivel de datos y estos son inmutables.

Número de serie

Algunos proyectos - seguro que todos los tuyos - utilizan el número de serie para tener claves primarias estables, ya que los número de series son claves técnicas y no están relacionados con los contenidos de su entidad.

Por tanto, el número de serie (ID de serie) es un número que se incrementa siempre que insertas una fila, y la base de datos hace todo el trabajo de forma transparente para el desarrollador. Es una clave primaria genial para tus entidades.

Pero como hemos dicho antes, tiene algunos problemas y los principales problemas son los siguientes;

  • Divulgación de la información. El valor actual corresponde al último valor usado como clave primaria y, si no hay eliminaciones, corresponde también al número de líneas en la tabla. Normalmente este valor de la clave primaria es el que se utiliza en URLs, y cualquier usuario tiene acceso a ella. Si alguien quiere saber, por ejemplo, cuántos usuarios tienes en tu base de datos de tu SaaS, lo único que tiene que hacer es crear una cuenta y mirar la URL. Los resultados pueden ser catastróficos a nivel de seguridad.
  • Enumeraciones de entidades. Otro problema, relacionado con el anterior, es que es muy fácil enumerar las entidades en una tabla, ya que empiezas con el valor 1 y vas incrementando el valor. Así es muy fácil “rascar” datos de todas tus entidades, ya que cualquier spammer que descubra que no controlas correctamente el acceso al contenido puede acceder a esta información, también mediante una URL, de cualquier entidad.
  • No unicidad entre tablas. Cada tabla tiene su propia secuencia y cada valor idéntico (p.e.1) se puede encontrar como clave primaria de entidades diferentes. Si te equivocas eliminando una línea de otra entidad cualquiera en tu base de datos, tendrás problemas. Estos problemas son aún mayores si tienes configuradas reglas ON DELETE CASCADE.

¿Cuál es la solución?

Creo que en el mundo IT todos los problemas tienen solución. Podrías hacer algún workaround, como por ejemplo empezar la secuencia en un punto arbitrario, o que el valor de incremento sea mayor que uno, o incluso compartir la secuencia entre las tablas. Lo que pasa es que seguirás divulgando información sobre el crecimiento de la base de datos y alcanzarás rápidamente el desbordamiento de enteros.

Es aquí donde entran en escena los UUIDs (Identificadores universalmente únicos) que hemos referido al inicio de este artículo. Con un UUID envés de tener el valor 1, puedes tener el valor 66d429fb-7796-4c88-9747-7f59ff56ee34.

Este valor es mucho más difícil de predecir, es universalmente único (en toda la base de datos) y es el candidato ideal para ser la clave primaria única en toda base de datos. Los UUID son valores de 128 bits, con representación textual en dígitos hexadecimales.

¿Por qué los UUIDs pueden ser una buena alternativa?

Los UUIDs, por ejemplo los UUIDv4, se basan en la aleatoriedad. Esto significa que no puedes garantizar la unicidad, pero la probabilidad de colisión es muy pequeña.

La aleatoriedad de los UUIDv4 te proporciona las siguientes características:

  • No puedes enumerar valores
  • No puedes saber cuántos valores hay actualmente en cada tabla
  • No tienes que comunicar con la base de datos para construir un valor

Creo que ya puedes entender las ventajas, pero no tener que comunicarte con la base de datos para construir un valor te permite insertar diferentes entidades relacionadas sin tener que esperar por las claves primarias y las claves externas.

De hecho utilizando UUIDv4, podrías generar entidades completas sin comunicarte con la base de datos, mejorando en la limpieza de código ya que no tendrías que gestionar entidades creadas parcialmente.  

Otro problema que solucionas utilizando UUIDs es que no tienes que lidiar con problemas de overflow relacionados con tu número de serie numérico de 32 bits, ni migraciones peligrosas a bases de datos de producción buscando enteros de 64 bits.

¿Cómo generar UUIDs de forma fácil?

Recuerda que en sistemas como MySQL o PostgreSQL tienes disponibles tipos de columna uuid que te permiten generar uuids de forma fácil. Si por casualidad el motor que utilizas para tus bases de datos no lo permite hacer de forma nativa para valores de  UUIDv4, puedes usar extensiones para lograrlo.

También puedes generar UUIDs en tu lenguaje o framework de programación preferido, utilizando Java, Scala, Node.js, Python, o PHP.

¿Qué te parece utilizar UUIDs como claves primarias envés del tradicional ID? Coméntalo abajo!

Foto: @dimhou de @unsplash

Fuentes: