Usando UUIDs en lugar de Integer Autoincrement Primary Keys con SQLAlchemy y MariaDb
UUID Primary Keys son "únicos en el universo" y tienen muchas ventajas. Considere usarlos en su próximo proyecto.
Todos conocemos el Integer Autoincrement Primary Key. Funciona muy bien, mucho software lo hace de esta manera. Es rápido. Pero también tiene limitaciones.
Una alternativa para el Integer Autoincrement Primary Key es el UUID (Universally Unique IDentifier) Primary Key. Un UUID es un número de 128 bits. Puede ser representado por una cadena, por ejemplo:
05390f2b-48e8-45c7-8ae4-08272dc69122
o una cadena de hexágonos (la misma que arriba con el carácter de guión eliminado):
05390f2b48e845c78ae408272dc69122
o un valor binario.
UUID son únicos, no hay ninguna numeración involucrada. Si usted genera un UUID en su laptop en Londres y su colega en Nueva York genera un UUID en su laptop, entonces ambos UUIDs son únicos, nunca pueden ser los mismos. Hay varias versiones de UUIDs, aquí vamos a usar la versión 4, también conocida como 'aleatoria' UUID. La probabilidad de una colisión es casi nula.
¿Son UUID Primary Keys una buena elección?
Creo que hay suficientes razones incluso para proyectos pequeños. Todo el mundo conoce el problema cuando se fusionan registros de bases de datos o tablas. Si sus tablas de base de datos utilizan Integer Autoincrement Primary Keys esto puede ser desordenado pero con UUIDs es pan comido.
Example#1: Blog con múltiples autores offline
Suponga que tiene este CMS / sitio web del blog y que tiene dos personas, escritores de entradas de blog, cada uno trabajando en una copia local, corriendo en sus computadoras portátiles. Entonces, ¿cómo se transfieren los nuevos registros al sistema de producción?
Si su base de datos utiliza Integer Autoincrement Primary Keys entonces debe ser extremadamente cuidadoso. Probablemente lo haga exportando algunos registros y luego importándolos en su base de datos de producción. No puedes importar los ID de los registros, ambos escritores pueden tener diferentes puestos con idénticos ID. Se hace más difícil cuando las entradas del blog se refieren a (nuevos) registros de una tabla que contiene los enlaces/referencias de una entrada del blog. Probablemente escribirás un guión que se ocupe de esto.
Si por el contrario estuviéramos usando UUID Primary Keys , simplemente exportamos los nuevos registros de ambos portátiles y los importamos en la base de datos de producción. No hay conflictos, es fácil.
Ejemplo#2: Tienda web con información de productos preparados fuera de línea
Supongamos que tienes una tienda web y tienes proveedores que entregan sus registros de información de productos preparados con UUID Primary Keys. Si su tienda web ya usa UUID Primary Keys esto es muy fácil de importar, sin conversiones.
Ventajas y desventajas de usar un UUID como clave primaria
Ventajas:
- Puede generar UUIDs en todas
partes Esto significa que puede crear registros sin conectarse a una base de datos - Los UUID son únicos a través de las tablas, bases de datos, sistemas
Esto facilita la fusión de registros de diferentes tablas, bases de datos - UUIDs hacen la replicación más fácil
Desventajas:
- Usa más memoria y espacio de disco, las tablas de índice se hacen más grandes
¿Pero a quién le importa esto hoy en día? - Más lento para SELECT con tablas grandes
Pero las optimizaciones son posibles - Puede ser mucho más lento para INSERT
Lleva más tiempo recalcular el índice, puede ser un problema serio - Más difícil de depurar debido a la falta de orden de las inserciones
Pero puedes añadir una columna DATETIME (microsegundos) 'created_on' y usar esto para ordenar.
UUIDs evitar la exposición de la información comercial (secretos)
Una importante ventaja adicional de usar una clave primaria UUID en lugar de una Integer Autoincrement Primary Key es que puedes preocuparte menos por exponer información comercial. Ejemplo: los contactos se almacenan en una tabla de contactos. Si esta tabla tiene un Integer Autoincrement Primary Key una vista de edición podría exponer el user_id en el URL:
/contact/edit/24
Esto significa que puedes adivinar el número de users de un sitio web. Si usas una clave primaria UUID entonces el user_id en el URL no expone nada:
/contact/edit/5489b4d7abd9416f9ba31d48fbb0f5be
Por supuesto, es mejor no exponer nunca ningún tipo de Primary Key. Esto puede parecer trivial pero puede causar mucho trabajo extra.
Ejemplo: considere que un registro de contacto contiene una Clave Extranjera para una ciudad. La Clave Extranjera del contacto hace referencia a la ciudad Primary Key. Si no queremos exponer el Primary Keys podemos añadir una columna Surrogate Key a cada tabla y generar un Surrogate Key para cada registro que creemos. Al editar el contacto debemos mostrar una lista desplegable con las ciudades disponibles. No queremos el Primary Keys de las ciudades expuestas y seleccionamos el Surrogate Keys en su lugar. Luego, cuando el formulario de contacto es enviado, debemos buscar el Primary Key de la Ciudad primero usando el valor Surrogate Key enviado. Una vez que recuperamos la Ciudad Primary Key podemos actualizar el Contacto.
Python y UUIDs
Generar UUIDs en Python es fácil:
>>> import uuid
>>> uuid_obj = uuid.uuid4()
>>> uuid_obj
UUID('05390f2b-48e8-45c7-8ae4-08272dc69122')
>>> str(uuid_obj)
'05390f2b-48e8-45c7-8ae4-08272dc69122'
>>> uuid_obj.hex
'05390f2b48e845c78ae408272dc69122'
También podemos convertir el hexágono UUID en una cadena UUID:
>>> uuid_obj2 = uuid.UUID(hex='05390f2b48e845c78ae408272dc69122', version=4)
>>> str(uuid_obj2)
'05390f2b-48e8-45c7-8ae4-08272dc69122'
Orden de los insertos
Al usar las claves primarias UUID perdemos el orden de los insertos. En mi modelo cada tabla tiene una columna MariaDb DATETIME 'created_on' que significa que la resolución mínima es de 1 segundo. Cuando el orden de las inserciones es importante podemos aumentar la resolución de la columna 'created_on' a microsegundos usando DATETIME(6). Si realmente necesitas el orden de inserción puedes añadir una columna de Autoincremento extra.
Performance
Usar UUIDs para las claves primarias no está realmente acelerando las consultas. Las consultas de SELECT se realizan más lentamente debido a la comparación de cadenas en lugar de la comparación de enteros. Las consultas INSERT pueden ser realmente lentas porque por cada inserción el índice debe ser reconstruido con números aleatorios. Hay optimizaciones posibles pero también tienen inconvenientes, como que las UUID ya no son "aleatorias".
SQLAlchemy, MariaDb, UUIDs y Alembic
Yo uso SQLAlchemy y MariaDb así que usemos un valor hexadecimal de 32 caracteres UUID. PostgreSQL ya soporta UUIDs fuera de la caja. Hay una receta en el sitio de SQLAlchemy : "Tipo de GUID agnóstico de respaldo", ver enlaces abajo. Incluí esta receta (script), con una pequeña modificación, en mi archivo models.py. Luego escribo mi modelo UUIDCity como:
class UUIDCity(Base):
__tablename__ = 'uuid_city'
id = Column(GUID(), primary_key=True, default=uuid.uuid4)
...
Esto se encarga de la generación de un UUID para un nuevo registro. Cambié un poco el script para asegurarme de que devuelva un valor de cadena y no un objeto GUID. Esto hace que sea mucho más fácil de usar en las consultas. (Pero puedo perder ventajas específicas de SQLAlchemy ?)
...
def process_result_value(self, value, dialect):
if value is None:
return value
else:
# 20200328/PPM commented the next two lines to return the db id as string
# if not isinstance(value, uuid.UUID):
# value = uuid.UUID(value)
return value
Hay un problema Alembic aquí. Cuando se ejecuta Alembic para el modelo de la ciudad UUIDCity:
alembic revision --autogenerate -m "some version text"
Alembic genera un archivo de versiones con:
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('uuid_demo_city',
sa.Column('id', shared.models.GUID(), nullable=False),
sa.Column('name', sa.String(length=250), server_default='', nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_uuid_demo_city_name'), 'uuid_demo_city', ['name'], unique=False)
Tengo una carpeta llamada "compartida" y en esta carpeta está el archivo models.py. Esto hará que el comando de actualización falle. El resultado del comando:
alembic upgrade head
es:
File "...", line 17, in upgrade
sa.Column('id', shared.models.GUID(), nullable=False),
NameError: name 'shared' is not defined
Esto es un poco confuso pero tiene que ver con el hecho de que no hay ningún tipo de datos shared.models.GUID(). Puedes modificar env.py y script.py.mako. Hay alguna información en los siguientes enlaces. No lo he hecho todavía. Por el momento he editado el archivo de versiones y lo he cambiado:
sa.Column('id', shared.models.GUID(), nullable=False),
en:
sa.Column('id', sa.CHAR(32), nullable=False),
Ahora el comando de actualización se ejecuta sin errores. También tenemos que seguir este procedimiento para las Claves Extranjeras. Por supuesto que es mejor modificar los scripts mencionados anteriormente, pero esta edición es sólo una vez al crear el modelo.
Resumen
¿Cómo llegué a escribir este post? Creo que estaba buscando formas de generar códigos aleatorios únicos. En Python usando UUIDs es fácil. Puede que haya escrito un código aleatorio único + MySQL. Entonces también golpeaste el UUID Primary Keys. Leí sobre ellos en el pasado, pero esta vez me tomé el tiempo de investigar este tema.
Si está ejecutando una base de datos con Integer Autoincrement Primary Keys , ¿merece la pena convertirla para usar UUID Primary Keys? Creo que no. Puede llevar mucho tiempo y puedes meterte en muchos problemas.
Pero para las nuevas bases de datos, incluso las muy pequeñas, recomiendo usar UUID Primary Keys. Nunca sabes si alguna vez te encontrarás en una situación en la que tengas que fusionar tablas, importar registros con claves UUID , hacer que las personas trabajen fuera de línea con tu base de datos.
Por supuesto que hay excepciones. Supongamos que muestras imágenes de webcam y guardas sus referencias en un registro de la base de datos. En este caso quieres el máximo rendimiento de INSERT , así que no uses UUID Primary Keys aquí!
Un sitio web de alto rendimiento con una gran base de datos típicamente no tiene que sufrir de un UUID Primary Keys más lento. SELECT es sólo un poco más lento y si se diseña adecuadamente, la mayoría de las solicitudes (SELECT) se sirven desde la caché.
Para este sitio web de CMS/blog decidí (muy) lentamente convertir el actual Integer Autoincrement Primary Keys a UUID Primary Keys.
Enlaces / créditos
Alembic: How to migrate custom type in a model?
https://stackoverflow.com/questions/15668115/alembic-how-to-migrate-custom-type-in-a-model
Backend-agnostic GUID Type
https://docs.sqlalchemy.org/en/13/core/custom_types.html#backend-agnostic-guid-type
Best UUID data type?
https://www.memsql.com/forum/t/best-uuid-data-type/182
Configuring MySQL to allow generation of Primary Key values
https://stackoverflow.com/questions/21669815/configuring-mysql-to-allow-generation-of-primary-key-values
GUID/UUID Performance
https://mariadb.com/kb/en/guiduuid-performance/
How can I bind a list to a parameter in a custom query in sqlalchemy?
https://stackoverflow.com/questions/13190392/how-can-i-bind-a-list-to-a-parameter-in-a-custom-query-in-sqlalchemy
How can I use UUIDs in SQLAlchemy?
https://stackoverflow.com/questions/183042/how-can-i-use-uuids-in-sqlalchemy
Microseconds in MariaDB
https://mariadb.com/kb/en/microseconds-in-mariadb/
UUIDs are Popular, but Bad for Performance — Let’s Discuss
https://www.percona.com/blog/2019/11/22/uuids-are-popular-but-bad-for-performance-lets-discuss/
Leer más
Alembic MariaDB SQLAlchemy UUID
Deje un comentario
Comente de forma anónima o inicie sesión para comentar.
Comentarios (1)
Deje una respuesta.
Responda de forma anónima o inicie sesión para responder.
Thanks for your post! I finally got my challenge solved by also commenting the 2 lines in the GUID class. It was a pain to get UUID objects back instead of plain UUID strings.
Recientes
- Un conmutador de base de datos con HAProxy y el HAProxy Runtime API
- Docker Swarm rolling updates
- Cómo ocultar las claves primarias de la base de datos UUID de su aplicación web
- Don't Repeat Yourself (DRY) con Jinja2
- SQLAlchemy, PostgreSQL, número máximo de filas por user
- Mostrar los valores en filtros dinámicos SQLAlchemy
Más vistos
- Usando Python's pyOpenSSL para verificar los certificados SSL descargados de un host
- Usando PyInstaller y Cython para crear un ejecutable de Python
- Reducir los tiempos de respuesta de las páginas de un sitio Flask SQLAlchemy web
- Conectarse a un servicio en un host Docker desde un contenedor Docker
- Usando UUIDs en lugar de Integer Autoincrement Primary Keys con SQLAlchemy y MariaDb
- SQLAlchemy: Uso de Cascade Deletes para eliminar objetos relacionados