¿Las claves naturales proporcionan un rendimiento mayor o menor en SQL Server que las claves enteras sustitutas?

25

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:

ingrese la descripción de la imagen aquí

¿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í .

Max Vernon
fuente
1
Bueno, el INT es de 4 bytes y el NVARCHAR efectivo (25) es aproximadamente 14 veces más largo (incluidos los datos del sistema, como la longitud), por lo que en términos de índice solo, creo que tendría un índice PK significativamente más amplio y profundo y, por lo tanto, más I / O es necesario, lo que afectará el tiempo de procesamiento. Sin embargo, un número entero natural (tal vez incluso un dígito de verificación) sería más o menos el mismo INT que pensamos usar para una columna de Identidad sustituta. Entonces, la "clave natural" puede ser un INT, BIGINT, CHAR, NVARCHAR y todo eso importa.
RLF
77
Creo que la ganancia de rendimiento que estaba obteniendo @ MikeSherrill'Catcall 'es que en realidad no necesitas la unión contra la tabla de "búsqueda" cuando usas una clave natural. Compare una consulta para obtener el valor de búsqueda con una combinación, con una consulta donde el valor ya está almacenado en la tabla principal. Es posible que obtenga un "ganador" diferente según la longitud de la clave natural y el número de filas en la tabla de búsqueda.
Mikael Eriksson
3
Lo que dijo @MikaelEriksson más los casos cuando tiene una unión entre más de 2 tablas (digamos 4) donde con los sustitutos tendrá que unir las tablas A a D a través de B y C, mientras que con las teclas naturales puede unir A a D directamente
ypercubeᵀᴹ

Respuestas:

18

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, UNIQUEla 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.

Sebastian Meine
fuente
10

Creo que lo mejor está en el medio .

Resumen de claves naturales:

  1. Hacen que el modelo de datos sea más obvio porque provienen del área temática y no de la cabeza de alguien.
  2. Las teclas simples (una columna, entre CHAR(4)y CHAR(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).
  3. Muchos casos, cuando las claves naturales son complejas: consta de dos o más columnas. Si dicha clave puede migrar a otra entidad como clave externa, entonces agregará sobrecarga de datos (los índices y las columnas de datos pueden aumentar) y el rendimiento se perderá.
  4. Si la clave es una cadena grande, entonces probablemente siempre se perderá con una clave entera, porque la simple condición de búsqueda se convierte en una comparación de matriz de bytes en un motor de base de datos, que en la mayoría de los casos es más lenta que la comparación de enteros.
  5. Si la clave es una cadena multilenguaje, entonces también debe ver las intercalaciones.

Beneficios: 1 y 2.

Vigilancias: 3, 4 y 5.


Resumen de claves de identidad artificial:

  1. 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 CASCADEpueden omitir operaciones personalizadas como porque los valores clave no cambian.

  2. 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.

  3. 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 como INT 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:

  • Si contiene una cadena grande, entonces es más lenta y agregará sobrecarga de datos si se migra como ajeno a otra entidad.
  • Si consta de varias columnas, entonces es más lento y agregará sobrecarga de datos si se migra como ajeno a otra entidad.
Bombardeo aéreo
fuente
55
"Las operaciones personalizadas como ON UPDATE CASCADE pueden omitirse porque los valores clave no cambian". El efecto de las claves sustitutas es hacer que cada referencia de clave externa sea equivalente a "EN ACTUALIZACIÓN EN CASCADA". La clave no cambia, pero el valor que representa .
Mike Sherrill 'Cat Recall'
@ MikeSherrill'Catcall 'Sí, por supuesto. Sin embargo, ON UPDATE CASCADEno se utiliza, mientras que las claves nunca se han actualizado. Pero, si lo son, entonces podría ser un problema si ON UPDATE NO ACTIONestá configurado. Quiero decir, que DBMS nunca lo usa, mientras que los valores de las columnas clave no cambian.
BlitZ
4

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.

SELECT t2.NaturalTable2Key, t2.NaturalTable1Key
FROM Table2 t2;

A su equivalente lógico si el atributo NaturalTable1Key en Table2 se reemplaza con el sustituto IDTable1Key:

SI.

SELECT t2.NaturalTable2Key, t1.NaturalTable1Key
FROM Table2 t2
INNER JOIN Table1 t1
ON t1.IDTable1Key = t2.IDTable1Key;

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.

nvogel
fuente