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.
fuente
Respuestas:
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
fuente
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 tener
on 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.
fuente
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
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.
fuente
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:
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.
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
fuente