Mysql int vs varchar como clave principal (InnoDB Storage Engine?

13

Estoy creando una aplicación web (sistema de gestión de proyectos) y me he estado preguntando sobre esto en lo que respecta al rendimiento.

Tengo una tabla de problemas y en su interior hay 12 claves externas que se vinculan a otras tablas. De ellos, 8 de ellos tendrían que unirme para obtener el campo de título de las otras tablas para que el registro tenga sentido en una aplicación web, pero luego significa hacer 8 uniones, lo que parece realmente excesivo, especialmente porque solo estoy llegando 1 campo para cada una de esas uniones.

Ahora también me han dicho que use una clave primaria de incremento automático (a menos que la división sea una preocupación, en cuyo caso debería usar un GUID) por razones de permanencia, pero ¿qué tan malo es usar un rendimiento varchar (longitud máxima 32)? Quiero decir que la mayoría de estas tablas probablemente no tendrán muchos registros (la mayoría de ellos deberían ser menores de 20). Además, si uso el título como la clave principal, no tendré que unirme el 95% del tiempo, por lo que para el 95% del sql, incluso se produciría cualquier golpe de rendimiento (creo). El único inconveniente que se me ocurre es que tendré un mayor uso de espacio en disco (pero un día es realmente un gran problema).

La razón por la que uso tablas de búsqueda para muchas de estas cosas en lugar de enumeraciones es porque necesito que todos estos valores sean configurables por el usuario final a través de la aplicación misma.

¿Cuáles son las desventajas de usar un varchar como clave principal para una tabla que no tiene la excepción de tener muchos registros?

ACTUALIZACIÓN - Algunas pruebas

Así que decidí hacer algunas pruebas básicas sobre estas cosas. Tengo 100000 registros y estas son las consultas básicas:

Consulta básica de VARCHAR FK

SELECT i.id, i.key, i.title, i.reporterUserUsername, i.assignedUserUsername, i.projectTitle, 
i.ProjectComponentTitle, i.affectedProjectVersionTitle, i.originalFixedProjectVersionTitle, 
i.fixedProjectVersionTitle, i.durationEstimate, i.storyPoints, i.dueDate, 
i.issueSecurityLevelId, i.creatorUserUsername, i.createdTimestamp, 
i.updatedTimestamp, i.issueTypeId, i.issueStatusId
FROM ProjectManagement.Issues i

Base INT FK Query

SELECT i.id, i.key, i.title, ru.username as reporterUserUsername, 
au.username as assignedUserUsername, p.title as projectTitle, 
pc.title as ProjectComponentTitle, pva.title as affectedProjectVersionTitle, 
pvo.title as originalFixedProjectVersionTitle, pvf.title as fixedProjectVersionTitle, 
i.durationEstimate, i.storyPoints, i.dueDate, isl.title as issueSecurityLevelId, 
cu.username as creatorUserUsername, i.createdTimestamp, i.updatedTimestamp, 
it.title as issueTypeId, is.title as issueStatusId
FROM ProjectManagement2.Issues i
INNER JOIN ProjectManagement2.IssueTypes `it` ON it.id = i.issueTypeId
INNER JOIN ProjectManagement2.IssueStatuses `is` ON is.id = i.issueStatusId
INNER JOIN ProjectManagement2.Users `ru` ON ru.id = i.reporterUserId
INNER JOIN ProjectManagement2.Users `au` ON au.id = i.assignedUserId
INNER JOIN ProjectManagement2.Users `cu` ON cu.id = i.creatorUserId
INNER JOIN ProjectManagement2.Projects `p` ON p.id = i.projectId
INNER JOIN ProjectManagement2.`ProjectComponents` `pc` ON pc.id = i.projectComponentId
INNER JOIN ProjectManagement2.ProjectVersions `pva` ON pva.id = i.affectedProjectVersionId
INNER JOIN ProjectManagement2.ProjectVersions `pvo` ON pvo.id = i.originalFixedProjectVersionId
INNER JOIN ProjectManagement2.ProjectVersions `pvf` ON pvf.id = i.fixedProjectVersionId
INNER JOIN ProjectManagement2.IssueSecurityLevels isl ON isl.id = i.issueSecurityLevelId

También ejecuté estas consultas con las siguientes adiciones:

  • Seleccione un elemento específico (donde i.key = 43298)
  • Agrupar por i.id
  • Ordenar por (it.title para int FK, i.issueTypeId para varchar FK)
  • Límite (50000, 100)
  • Agrupar y limitar juntos
  • Agrupar, ordenar y limitar juntos

Los resultados para estos donde:

TIPO DE CONSULTA: VARCHAR FK TIME / INT FK TIME


Consulta base: ~ 4ms / ~ 52ms

Seleccione un elemento específico: ~ 140ms / ~ 250ms

Agrupar por i.id: ~ 4ms / ~ 2.8sec

Ordenar por: ~ 231 ms / ~ 2 segundos

Límite: ~ 67ms / ~ 343ms

Agrupar y limitar juntos: ~ 504 ms / ~ 2 segundos

Agrupar, ordenar y limitar juntos: ~ 504ms /~2.3sec

Ahora no sé qué configuración podría hacer para que uno u otro (o ambos) sean más rápidos, pero parece que el VARCHAR FK ve más rápido en las consultas de datos (a veces mucho más rápido).

Supongo que tengo que elegir si esa mejora de la velocidad vale el tamaño extra de datos / índice.

ryanzec
fuente
Su prueba indica algo. También probaría con varias configuraciones de InnoDB (agrupaciones de almacenamiento intermedio, etc.) porque la configuración predeterminada de MySQL no está realmente optimizada para InnoDB.
ypercubeᵀᴹ
También debe probar el rendimiento de Insertar / Actualizar / Eliminar ya que esto también puede verse afectado por el tamaño del índice. La clave agrupada de cada tabla de InnoDB suele ser la PK y esta columna (PK) también se incluye en todos los demás índices. Esta es probablemente una gran desventaja de las grandes PK en InnoDB y muchos índices en la tabla (pero 32 bytes es bastante medio, no grande, por lo que puede no ser un problema).
ypercubeᵀᴹ
También debe probar con tablas más grandes (en el rango de digamos 10-100M filas, o más grandes), si espera que sus tablas crezcan más de 100K (que no es realmente grande).
ypercubeᵀᴹ
@ypercube Así que aumento los datos a 2 millones y la instrucción select para el int FK se vuelve más lenta exponencialmente donde la clave externa varchar permanece bastante estable. Creo que vale la pena el precio de varchar en los requisitos de disco / memoria para la ganancia en consultas seleccionadas (que será fundamental en esta tabla en particular y en algunas otras).
ryanzec
Simplemente revise su configuración de db (y particularmente InnoDB) también, antes de llegar a conclusiones. Con tablas de referencia pequeñas, no esperaría un aumento exponencial
ypercubeᵀᴹ

Respuestas:

9

Sigo las siguientes reglas para las claves principales:

a) No debe tener ningún significado comercial: deben ser totalmente independientes de la aplicación que está desarrollando, por lo tanto, elijo números enteros generados automáticamente. Sin embargo, si necesita columnas adicionales para ser único, cree índices únicos para admitir eso

b) Debería funcionar en combinaciones: unirse a varchars vs enteros es aproximadamente 2x a 3x más lento a medida que aumenta la longitud de la clave primaria, por lo que desea tener sus claves como enteros. Dado que todos los sistemas informáticos son binarios, sospecho que es porque la cadena se cambia a binaria y luego se compara con los demás, lo cual es muy lento

c) Use el tipo de datos más pequeño posible: si espera que su tabla tenga muy pocas columnas que digan 52 estados de EE. UU., entonces use el tipo más pequeño posible, tal vez un CHAR (2) para el código de 2 dígitos, pero aún así buscaría un tinyint (128) para la columna frente a un int grande que puede llegar a 2 billones

También tendrá un desafío con la conexión en cascada de los cambios de las claves principales a las otras tablas si, por ejemplo, el nombre del proyecto cambia (lo cual no es raro)

Busque números enteros de incremento automático secuencial para sus claves primarias y obtenga las eficiencias incorporadas que los sistemas de bases de datos brindan soporte para cambios en el futuro

Stephen Senkomago Musoke
fuente
1
Las cadenas no se cambian a binarias; se almacenan en binario desde el principio. ¿De qué otra forma serían almacenados? ¿Quizás está pensando en operaciones para permitir la comparación entre mayúsculas y minúsculas?
Jon of All Trades
6

En sus pruebas, no está comparando la diferencia de rendimiento de las teclas varchar vs int, sino el costo de múltiples combinaciones. No es sorprendente que consultar 1 tabla sea más rápido que unir muchas tablas.
Una desventaja de la clave primaria varchar es el aumento del tamaño del índice, como señaló atxdba . Incluso si su tabla de búsqueda no tiene ningún otro índice excepto PK (que es bastante improbable, pero posible), cada tabla que haga referencia a la búsqueda tendrá un índice en esta columna.
Otra cosa mala de las claves primarias naturales es que su valor puede cambiar y causar muchas actualizaciones en cascada. No todos los RDMS, por ejemplo Oracle, incluso te permiten teneron update cascade. En general, cambiar el valor de la clave primaria se considera como una muy mala práctica. No quiero decir que las claves primarias naturales son siempre malas; Si los valores de búsqueda son pequeños y nunca cambian, creo que puede ser aceptable.

Una opción que puede considerar es implementar una vista materializada. Mysql no lo admite directamente, pero puede lograr la funcionalidad deseada con disparadores en las tablas subyacentes. Entonces tendrá una tabla que tiene todo lo que necesita para mostrar. Además, si el rendimiento es aceptable, no luche con el problema que no existe en este momento.

a1ex07
fuente
3

El mayor inconveniente es la repetición de la PK. Usted señaló un aumento en el uso del espacio en disco, pero para ser claros, el mayor tamaño del índice es su mayor preocupación. Dado que innodb es un índice agrupado, cada índice secundario almacena internamente una copia de la PK que utiliza para finalmente encontrar registros coincidentes.

Usted dice que se espera que las tablas sean "pequeñas" (de hecho, 20 filas son muy pequeñas). Si tiene suficiente RAM para establecer el innodb_buffer_pool_size igual a

select sum(data_length+index_length) from information_schema.tables where engine='innodb';

Entonces haz eso y probablemente estarás sentada bonita. Como regla general, sin embargo, querría dejar al menos 30% - 40% de la memoria total del sistema para otros gastos indirectos de mysql y des cache. Y eso supone que es un servidor de DB dedicado. Si tiene otras cosas ejecutándose en el sistema, también deberá tener en cuenta sus requisitos.

atxdba
fuente
1

Además de la respuesta @atxdba, que explicaba por qué usar numérico sería mejor para el espacio en disco, quería agregar dos puntos:

  1. Si su tabla Issues está basada en VARCHAR FK, y digamos que tiene 20 VARCHAR (32) FK pequeños, su registro puede llegar a 20x32bytes de longitud, mientras que como mencionó, las otras tablas son tablas de búsqueda, por lo que INT FK podría ser TINYINT FK que hace para 20 campos a 20 bytes de registros. Sé que para varios cientos de registros no cambiará mucho, pero cuando llegue a varios millones, supongo que apreciará el ahorro de espacio.

  2. Para el problema de la velocidad, consideraría usar índices de cobertura, ya que parece que para esta consulta no está recuperando esa cantidad de datos de las tablas de búsqueda, iría por el índice de cobertura y volvería a hacer la prueba que proporcionó con VARCHAR FK / W / COVERING ÍNDICE Y INT FK regular.

Espero que pueda ayudar

Spredzy
fuente