Le chiavi naturali forniscono prestazioni superiori o inferiori in SQL Server rispetto alle chiavi intere surrogate?


25

Sono un fan delle chiavi surrogate. C'è il rischio che le mie scoperte siano di parte confermate.

Molte domande che ho visto sia qui che su http://stackoverflow.com usano chiavi naturali anziché chiavi surrogate basate su IDENTITY()valori.

Il mio background nei sistemi informatici mi dice che eseguire qualsiasi operazione comparativa su un numero intero sarà più veloce del confronto tra stringhe.

Questo commento mi ha fatto mettere in discussione le mie convinzioni, quindi ho pensato di creare un sistema per investigare la mia tesi secondo cui gli interi sono più veloci delle stringhe da utilizzare come chiavi in ​​SQL Server.

Poiché è probabile che ci sia una differenza molto esigua nei set di dati di piccole dimensioni, ho immediatamente pensato a una configurazione a due tabelle in cui la tabella principale ha 1.000.000 di righe e la tabella secondaria ha 10 righe per ogni riga nella tabella primaria per un totale di 10.000.000 di righe in la tabella secondaria. La premessa del mio test è quella di creare due set di tabelle come questa, una usando chiavi naturali e una usando chiavi intere, ed eseguire test di temporizzazione su una semplice query come:

SELECT *
FROM Table1
    INNER JOIN Table2 ON Table1.Key = Table2.Key;

Di seguito è riportato il codice che ho creato come banco di prova:

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

Il codice sopra crea un database e 4 tabelle e riempie le tabelle di dati, pronti per il test. Il codice di test che ho eseguito è:

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;

Questi sono i risultati:

inserisci qui la descrizione dell'immagine

Sto facendo qualcosa di sbagliato qui, o i tasti INT sono 3 volte più veloci dei tasti naturali a 25 caratteri?

Nota, ho scritto una domanda di follow-up qui .


1
Bene, l'INT è di 4 byte e l'effettiva NVARCHAR (25) è circa 14 volte più lunga (compresi i dati di sistema come la lunghezza), quindi in termini di solo indice credo che avresti un indice PK significativamente più ampio e profondo e quindi più I / O è necessario e ciò influirà sui tempi di elaborazione. Tuttavia, un numero intero naturale (forse anche un controllo numerico) sarebbe praticamente lo stesso INT che pensiamo di usare per una colonna Identity surrogata. Quindi, la "chiave naturale" potrebbe essere un INT, BIGINT, CHAR, NVARCHAR e tutto ciò che conta.
RLF,

7
Penso che il guadagno in termini di prestazioni @ MikeSherrill'Catcall 'stia ottenendo è che in realtà non hai bisogno del join rispetto alla tabella "lookup" quando usi una chiave naturale. Confronta una query per ottenere il valore di ricerca con un join, con una query in cui il valore è già memorizzato nella tabella principale. Potresti ottenere un "vincitore" diverso a seconda della lunghezza della chiave naturale e del numero di righe nella tabella di ricerca.
Mikael Eriksson,

3
Cosa ha detto @MikaelEriksson oltre ai casi in cui hai un join tra più di 2 tabelle (diciamo 4) in cui con i surrogati dovrai unire le tabelle da A a D attraverso B e C mentre con le chiavi naturali potresti unire direttamente da A a D
ypercubeᵀᴹ

Risposte:


18

In generale, SQL Server utilizza alberi B + per gli indici. Il costo di una ricerca di indice è direttamente correlato alla lunghezza della chiave in questo formato di archiviazione. Quindi, una chiave surrogata di solito supera di gran lunga una chiave naturale nelle ricerche di indice.

SQL Server raggruppa una tabella sulla chiave primaria per impostazione predefinita. La chiave di indice cluster viene utilizzata per identificare le righe, quindi viene aggiunta come colonna inclusa a ogni altro indice. Maggiore è la chiave, maggiore è ogni indice secondario.

Ancora peggio, se gli indici secondari non sono definiti esplicitamente come UNIQUEla chiave di indice cluster diventa automaticamente parte della chiave di ciascuno di essi. Ciò si applica in genere alla maggior parte degli indici, in quanto di solito gli indici vengono dichiarati unici solo quando è richiesto il rispetto dell'unicità.

Quindi, se la domanda è: indice naturale contro surrogato raggruppato, il surrogato vincerà quasi sempre.

D'altra parte, stai aggiungendo quella colonna surrogata alla tabella rendendo la tabella in sé più grande. Ciò farà sì che le scansioni dell'indice cluster diventino più costose. Quindi, se hai solo pochissimi indici secondari e il tuo carico di lavoro richiede di guardare spesso tutte (o la maggior parte delle) righe, potresti effettivamente essere meglio con una chiave naturale che salva quei pochi byte extra.

Infine, le chiavi naturali spesso rendono più semplice la comprensione del modello di dati. Mentre si utilizza più spazio di archiviazione, le chiavi primarie naturali portano a chiavi esterne naturali che a loro volta aumentano la densità di informazioni locali.

Quindi, come spesso accade nel mondo dei database, la vera risposta è "dipende". E - prova sempre nel tuo ambiente con dati realistici.


10

Credo che il meglio sia nel mezzo .

Panoramica delle chiavi naturali:

  1. Esse rendono il modello di dati più ovvio perché provengono dall'area tematica e non dalla testa di qualcuno.
  2. Le chiavi semplici (una colonna, tra CHAR(4)e CHAR(20)) stanno salvando alcuni byte extra, ma devi controllarne la coerenza ( ON UPDATE CASCADEdiventa fondamentale per quelle chiavi, che potrebbero essere cambiate).
  3. Molti casi, quando le chiavi naturali sono complesse: sono composte da due o più colonne. Se tale chiave potrebbe migrare verso un'altra entità come chiave anticipata, allora si aggiungerà un sovraccarico di dati (gli indici e le colonne di dati potrebbero diventare di grandi dimensioni) e le prestazioni ridotte.
  4. Se la chiave è una stringa di grandi dimensioni, probabilmente perderà sempre in una chiave intera, perché una semplice condizione di ricerca diventa un confronto di array di byte in un motore di database, che nella maggior parte dei casi è più lento, rispetto al confronto di numeri interi.
  5. Se key è una stringa in più lingue, è necessario guardare anche le regole di confronto.

Vantaggi: 1 e 2.

Watchouts: 3, 4 e 5.


Panoramica delle chiavi di identità artificiale:

  1. Non è necessario preoccuparsi della loro creazione e gestione (nella maggior parte dei casi) poiché questa funzione è gestita dal motore di database. Sono unici per impostazione predefinita e non occupano molto spazio. Operazioni personalizzate come ON UPDATE CASCADEpotrebbero essere ommited, perché i valori chiave non cambiano.

  2. Sono (spesso) i migliori candidati alla migrazione come chiavi esterne perché:

    2.1. è costituito da una colonna;

    2.2. utilizzando un tipo semplice che ha un peso ridotto e agisce rapidamente per le operazioni di confronto.

  3. Per le entità di un'associazione, le cui chiavi non vengono migrate da nessuna parte, potrebbe diventare un puro sovraccarico di dati, poiché si perde l'utilità. La chiave primaria naturale complessa (se non ci sono colonne stringa) sarà più utile.

Vantaggi: 1 e 2.

Watchouts: 3.


CONCLUSIONE:

Le chiavi arificial sono più gestibili, affidabili e veloci perché sono state progettate per questa funzionalità. Ma in alcuni casi non sono necessari. Ad esempio, il CHAR(4)candidato a colonna singola nella maggior parte dei casi si comporta come INT IDENTITY. Quindi c'è un'altra domanda anche qui: manutenibilità + stabilità o ovvietà ?

Domanda "Devo iniettare una chiave artificiale o no?" dipende sempre dalla struttura chiave naturale:

  • Se contiene una stringa di grandi dimensioni, è più lenta e aggiungerà sovraccarico di dati se si esegue la migrazione come estranea a un'altra entità.
  • Se è composto da più colonne, è più lento e aggiungerà sovraccarico di dati se si esegue la migrazione come estraneo a un'altra entità.

5
"Le operazioni personalizzate come ON UPDATE CASCADE potrebbero essere disabilitate, perché i valori chiave non cambiano." L'effetto delle chiavi surrogate è di rendere ogni riferimento a chiave esterna l'equivalente di "AGGIORNAMENTO CASCADE". La chiave non cambia, ma il valore che rappresenta lo fa .
Mike Sherrill 'Cat Recall',

@ MikeSherrill'Catcall 'Sì, certo. Tuttavia, ON UPDATE CASCADEnon utilizzato, mentre le chiavi non sono mai state aggiornate. Ma, se lo sono, potrebbe essere un problema se ON UPDATE NO ACTIONè configurato. Voglio dire, quel DBMS non lo usa mai, mentre i valori della colonna chiave non sono cambiati.
BlitZ,

4

Una chiave è una caratteristica logica di un database, mentre le prestazioni sono sempre determinate dall'implementazione fisica nell'archiviazione e dalle operazioni fisiche eseguite contro tale implementazione. È quindi un errore attribuire le caratteristiche prestazionali alle chiavi.

In questo esempio particolare, tuttavia, due possibili implementazioni di tabelle e query vengono confrontate tra loro. L'esempio non risponde alla domanda posta qui nel titolo. Il confronto che si sta facendo è di unire usando due diversi tipi di dati (intero e carattere) usando solo un tipo di indice (albero B). Un punto "ovvio" è che se un indice di hash o un altro tipo di indice venissero usati, molto probabilmente non ci sarebbe alcuna differenza di prestazioni misurabile tra le due implementazioni. Tuttavia, ci sono problemi più fondamentali con l'esempio.

Due query vengono confrontate per le prestazioni, ma le due query non sono logicamente equivalenti perché restituiscono risultati diversi! Un test più realistico confronta due query che restituiscono gli stessi risultati ma utilizzano implementazioni diverse.

Il punto essenziale di una chiave surrogata è che si tratta di un attributo aggiuntivo in una tabella in cui la tabella ha anche attributi chiave "significativi" utilizzati nel dominio aziendale. Sono gli attributi non surrogati che sono utili per i risultati della query per essere utili. Un test realistico quindi confronterebbe le tabelle usando solo chiavi naturali con un'implementazione alternativa con chiavi sia naturali che surrogate nella stessa tabella. Le chiavi surrogate in genere richiedono ulteriore archiviazione e indicizzazione e per definizione richiedono ulteriori vincoli di unicità. I surrogati richiedono un'ulteriore elaborazione per mappare i valori della chiave naturale esterna sui loro surrogati e viceversa.

Ora confronta questa potenziale query:

UN.

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

Al suo equivalente logico se l'attributo NaturalTable1Key in Table2 viene sostituito con il surrogato IDTable1Key:

B.

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

La query B richiede un join; La query A no. Questa è una situazione familiare nei database che (sovra) usano surrogati. Le query diventano inutilmente complesse e molto più difficili da ottimizzare. La logica aziendale (in particolare i vincoli di integrità dei dati) diventa più difficile da implementare, testare e verificare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.