Soy fanático de las llaves sustitutas. Existe el riesgo de que mis hallazgos tengan sesgos de confirmación.
Muchas preguntas que he visto aquí y en http://stackoverflow.com usan claves naturales en lugar de claves sustitutas basadas en IDENTITY()
valores.
Mi experiencia en sistemas informáticos me dice que realizar cualquier operación comparativa en un entero será más rápido que comparar cadenas.
Este comentario me hizo cuestionar mis creencias, por lo que pensé en crear un sistema para investigar mi tesis de que los enteros son más rápidos que las cadenas para usar como claves en SQL Server.
Dado que es probable que haya muy poca diferencia discernible en conjuntos de datos pequeños, inmediatamente pensé en una configuración de dos tablas donde la tabla primaria tiene 1,000,000 filas y la tabla secundaria tiene 10 filas para cada fila en la tabla primaria para un total de 10,000,000 filas en La mesa secundaria. La premisa de mi prueba es crear dos conjuntos de tablas como esta, una con teclas naturales y otra con teclas enteras, y ejecutar pruebas de tiempo en una consulta simple como:
SELECT *
FROM Table1
INNER JOIN Table2 ON Table1.Key = Table2.Key;
El siguiente es el código que creé como banco de pruebas:
USE Master;
IF (SELECT COUNT(database_id) FROM sys.databases d WHERE d.name = 'NaturalKeyTest') = 1
BEGIN
ALTER DATABASE NaturalKeyTest SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE NaturalKeyTest;
END
GO
CREATE DATABASE NaturalKeyTest
ON (NAME = 'NaturalKeyTest', FILENAME =
'C:\SQLServer\Data\NaturalKeyTest.mdf', SIZE=8GB, FILEGROWTH=1GB)
LOG ON (NAME='NaturalKeyTestLog', FILENAME =
'C:\SQLServer\Logs\NaturalKeyTest.mdf', SIZE=256MB, FILEGROWTH=128MB);
GO
ALTER DATABASE NaturalKeyTest SET RECOVERY SIMPLE;
GO
USE NaturalKeyTest;
GO
CREATE VIEW GetRand
AS
SELECT RAND() AS RandomNumber;
GO
CREATE FUNCTION RandomString
(
@StringLength INT
)
RETURNS NVARCHAR(max)
AS
BEGIN
DECLARE @cnt INT = 0
DECLARE @str NVARCHAR(MAX) = '';
DECLARE @RandomNum FLOAT = 0;
WHILE @cnt < @StringLength
BEGIN
SELECT @RandomNum = RandomNumber
FROM GetRand;
SET @str = @str + CAST(CHAR((@RandomNum * 64.) + 32) AS NVARCHAR(MAX));
SET @cnt = @cnt + 1;
END
RETURN @str;
END;
GO
CREATE TABLE NaturalTable1
(
NaturalTable1Key NVARCHAR(255) NOT NULL
CONSTRAINT PK_NaturalTable1 PRIMARY KEY CLUSTERED
, Table1TestData NVARCHAR(255) NOT NULL
);
CREATE TABLE NaturalTable2
(
NaturalTable2Key NVARCHAR(255) NOT NULL
CONSTRAINT PK_NaturalTable2 PRIMARY KEY CLUSTERED
, NaturalTable1Key NVARCHAR(255) NOT NULL
CONSTRAINT FK_NaturalTable2_NaturalTable1Key
FOREIGN KEY REFERENCES dbo.NaturalTable1 (NaturalTable1Key)
ON DELETE CASCADE ON UPDATE CASCADE
, Table2TestData NVARCHAR(255) NOT NULL
);
GO
/* insert 1,000,000 rows into NaturalTable1 */
INSERT INTO NaturalTable1 (NaturalTable1Key, Table1TestData)
VALUES (dbo.RandomString(25), dbo.RandomString(100));
GO 1000000
/* insert 10,000,000 rows into NaturalTable2 */
INSERT INTO NaturalTable2 (NaturalTable2Key, NaturalTable1Key, Table2TestData)
SELECT dbo.RandomString(25), T1.NaturalTable1Key, dbo.RandomString(100)
FROM NaturalTable1 T1
GO 10
CREATE TABLE IDTable1
(
IDTable1Key INT NOT NULL CONSTRAINT PK_IDTable1
PRIMARY KEY CLUSTERED IDENTITY(1,1)
, Table1TestData NVARCHAR(255) NOT NULL
CONSTRAINT DF_IDTable1_TestData DEFAULT dbo.RandomString(100)
);
CREATE TABLE IDTable2
(
IDTable2Key INT NOT NULL CONSTRAINT PK_IDTable2
PRIMARY KEY CLUSTERED IDENTITY(1,1)
, IDTable1Key INT NOT NULL
CONSTRAINT FK_IDTable2_IDTable1Key FOREIGN KEY
REFERENCES dbo.IDTable1 (IDTable1Key)
ON DELETE CASCADE ON UPDATE CASCADE
, Table2TestData NVARCHAR(255) NOT NULL
CONSTRAINT DF_IDTable2_TestData DEFAULT dbo.RandomString(100)
);
GO
INSERT INTO IDTable1 DEFAULT VALUES;
GO 1000000
INSERT INTO IDTable2 (IDTable1Key)
SELECT T1.IDTable1Key
FROM IDTable1 T1
GO 10
El código anterior crea una base de datos y 4 tablas, y llena las tablas con datos, listos para probar. El código de prueba que ejecuté es:
USE NaturalKeyTest;
GO
DECLARE @loops INT = 0;
DECLARE @MaxLoops INT = 10;
DECLARE @Results TABLE (
FinishedAt DATETIME DEFAULT (GETDATE())
, KeyType NVARCHAR(255)
, ElapsedTime FLOAT
);
WHILE @loops < @MaxLoops
BEGIN
DBCC FREEPROCCACHE;
DBCC FREESESSIONCACHE;
DBCC FREESYSTEMCACHE ('ALL');
DBCC DROPCLEANBUFFERS;
WAITFOR DELAY '00:00:05';
DECLARE @start DATETIME = GETDATE();
DECLARE @end DATETIME;
DECLARE @count INT;
SELECT @count = COUNT(*)
FROM dbo.NaturalTable1 T1
INNER JOIN dbo.NaturalTable2 T2 ON T1.NaturalTable1Key = T2.NaturalTable1Key;
SET @end = GETDATE();
INSERT INTO @Results (KeyType, ElapsedTime)
SELECT 'Natural PK' AS KeyType, CAST((@end - @start) AS FLOAT) AS ElapsedTime;
DBCC FREEPROCCACHE;
DBCC FREESESSIONCACHE;
DBCC FREESYSTEMCACHE ('ALL');
DBCC DROPCLEANBUFFERS;
WAITFOR DELAY '00:00:05';
SET @start = GETDATE();
SELECT @count = COUNT(*)
FROM dbo.IDTable1 T1
INNER JOIN dbo.IDTable2 T2 ON T1.IDTable1Key = T2.IDTable1Key;
SET @end = GETDATE();
INSERT INTO @Results (KeyType, ElapsedTime)
SELECT 'IDENTITY() PK' AS KeyType, CAST((@end - @start) AS FLOAT) AS ElapsedTime;
SET @loops = @loops + 1;
END
SELECT KeyType, FORMAT(CAST(AVG(ElapsedTime) AS DATETIME), 'HH:mm:ss.fff') AS AvgTime
FROM @Results
GROUP BY KeyType;
Estos son los resultados:
¿Estoy haciendo algo mal aquí, o las teclas INT son 3 veces más rápidas que las teclas naturales de 25 caracteres?
Tenga en cuenta que he escrito una pregunta de seguimiento aquí .
Respuestas:
En general, SQL Server usa árboles B + para los índices. El gasto de una búsqueda de índice está directamente relacionado con la longitud de la clave en este formato de almacenamiento. Por lo tanto, una clave sustituta generalmente supera a una clave natural en las búsquedas de índice.
SQL Server agrupa una tabla en la clave primaria de forma predeterminada. La clave de índice agrupado se usa para identificar filas, por lo que se agrega como columna incluida a cualquier otro índice. Cuanto más ancha es esa clave, más grande es cada índice secundario.
Peor aún, si los índices secundarios no se definen explícitamente,
UNIQUE
la clave de índice agrupada se convierte automáticamente en parte de la clave de cada uno de ellos. Eso generalmente se aplica a la mayoría de los índices, ya que generalmente los índices se declaran como únicos solo cuando el requisito es hacer cumplir la unicidad.Entonces, si la pregunta es, índice agrupado natural versus sustituto, el sustituto casi siempre ganará.
Por otro lado, está agregando esa columna sustituta a la tabla haciendo que la tabla en sí misma sea más grande. Eso hará que los análisis de índice agrupados sean más caros. Por lo tanto, si tiene muy pocos índices secundarios y su carga de trabajo requiere examinar todas las filas (o la mayoría de ellas) con frecuencia, en realidad podría ser mejor con una clave natural que guarde esos pocos bytes adicionales.
Finalmente, las claves naturales a menudo facilitan la comprensión del modelo de datos. Al usar más espacio de almacenamiento, las claves primarias naturales conducen a claves externas naturales que a su vez aumentan la densidad de información local.
Entonces, como tan a menudo en el mundo de las bases de datos, la respuesta real es "depende". Y, siempre pruebe en su propio entorno con datos realistas.
fuente
Creo que lo mejor está en el medio .
Resumen de claves naturales:
CHAR(4)
yCHAR(20)
) están guardando algunos bytes adicionales, pero debe observar su coherencia (ON UPDATE CASCADE
vuelve crítico para esas teclas, que podrían modificarse).Beneficios: 1 y 2.
Vigilancias: 3, 4 y 5.
Resumen de claves de identidad artificial:
No necesita preocuparse por su creación y manejo (en la mayoría de los casos) ya que esta función es manejada por el motor de base de datos. Son únicos por defecto y no ocupan mucho espacio. Se
ON UPDATE CASCADE
pueden omitir operaciones personalizadas como porque los valores clave no cambian.Ellos (a menudo) son los mejores candidatos para la migración como claves foráneas porque:
2.1. consta de una columna;
2.2. usando un tipo simple que tiene un peso pequeño y actúa rápido para las operaciones de comparación.
Para una asociación de entidades, cuyas claves no se migran a ningún lado, podría convertirse en una sobrecarga de datos pura, ya que se pierde su utilidad. La clave primaria natural compleja (si no hay columnas de cadena allí) será más útil.
Beneficios: 1 y 2.
Cuidado: 3.
CONCLUSIÓN:
Las claves artificiales son más fáciles de mantener, confiables y rápidas porque han sido diseñadas para estas características. Pero en algunos casos no son necesarios. Por ejemplo, el
CHAR(4)
candidato de columna única en la mayoría de los casos se comporta comoINT IDENTITY
. Entonces hay otra pregunta aquí también: mantenibilidad + estabilidad u obviedad ?Pregunta "¿Debo inyectar una llave artificial o no?" siempre depende de la estructura clave natural:
fuente
ON UPDATE CASCADE
no se utiliza, mientras que las claves nunca se han actualizado. Pero, si lo son, entonces podría ser un problema siON UPDATE NO ACTION
está configurado. Quiero decir, que DBMS nunca lo usa, mientras que los valores de las columnas clave no cambian.Una clave es una característica lógica de una base de datos, mientras que el rendimiento siempre está determinado por la implementación física en el almacenamiento y por las operaciones físicas ejecutadas contra esa implementación. Por lo tanto, es un error atribuir características de rendimiento a las teclas.
Sin embargo, en este ejemplo particular, dos implementaciones posibles de tablas y consultas se comparan entre sí. El ejemplo no responde la pregunta que se plantea en el título aquí. La comparación que se realiza es de combinaciones que utilizan dos tipos de datos diferentes (entero y carácter) utilizando solo un tipo de índice (árbol B). Un punto "obvio" es que si se utilizara un índice hash u otro tipo de índice, posiblemente no habría una diferencia de rendimiento medible entre las dos implementaciones. Sin embargo, hay problemas más fundamentales con el ejemplo.
Se comparan dos consultas para el rendimiento, pero las dos consultas no son lógicamente equivalentes porque devuelven resultados diferentes. Una prueba más realista compararía dos consultas que devuelven lo mismo resultados pero que utilizan implementaciones diferentes.
El punto esencial sobre una clave sustituta es que es un atributo adicional en una tabla donde la tabla también tiene atributos clave "significativos" utilizados en el dominio comercial. Son los atributos no sustitutos los que son interesantes para que los resultados de la consulta sean útiles. Por lo tanto, una prueba realista compararía las tablas utilizando solo claves naturales con una implementación alternativa que tenga claves naturales y sustitutas en la misma tabla. Las claves sustitutas generalmente requieren almacenamiento e indexación adicionales y, por definición, requieren restricciones de unicidad adicionales. Los sustitutos requieren un procesamiento adicional para mapear los valores clave naturales externos en sus sustitutos y viceversa.
Ahora compare esta consulta potencial:
A.
A su equivalente lógico si el atributo NaturalTable1Key en Table2 se reemplaza con el sustituto IDTable1Key:
SI.
La consulta B requiere una unión; La consulta A no. Esta es una situación familiar en las bases de datos que (sobre) usan sustitutos. Las consultas se vuelven innecesariamente complejas y mucho más difíciles de optimizar. La lógica empresarial (especialmente las restricciones de integridad de datos) se vuelve más difícil de implementar, probar y verificar.
fuente