Je suis fan des clés de substitution. Il y a un risque que mes conclusions soient biaisées par la confirmation.
De nombreuses questions que j'ai vues ici et sur http://stackoverflow.com utilisent des clés naturelles au lieu de clés de substitution basées sur des IDENTITY()
valeurs.
Mon expérience dans les systèmes informatiques me dit que toute opération comparative sur un entier sera plus rapide que la comparaison de chaînes.
Ce commentaire m'a fait remettre en question mes croyances, j'ai donc pensé créer un système pour étudier ma thèse selon laquelle les entiers sont plus rapides que les chaînes à utiliser comme clés dans SQL Server.
Puisqu'il est probable qu'il y ait très peu de différence perceptible dans les petits ensembles de données, j'ai immédiatement pensé à une configuration à deux tables où la table principale a 1 000 000 lignes et la table secondaire a 10 lignes pour chaque ligne de la table principale pour un total de 10 000 000 lignes dans la table secondaire. La prémisse de mon test est de créer deux ensembles de tables comme celui-ci, un à l'aide de clés naturelles et un à l'aide de clés entières, et d'exécuter des tests de synchronisation sur une requête simple comme:
SELECT *
FROM Table1
INNER JOIN Table2 ON Table1.Key = Table2.Key;
Voici le code que j'ai créé comme banc d'essai:
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
Le code ci-dessus crée une base de données et 4 tables et remplit les tables de données, prêtes à être testées. Le code de test que j'ai exécuté est:
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;
Voici les résultats:
Suis-je en train de faire quelque chose de mal ici, ou les touches INT sont-elles 3 fois plus rapides que les clés naturelles à 25 caractères?
Remarque, j'ai écrit une question de suivi ici .
Réponses:
En général, SQL Server utilise des arborescences B + pour les index. Le coût d'une recherche d'index est directement lié à la longueur de la clé dans ce format de stockage. Par conséquent, une clé de substitution surpasse généralement une clé naturelle sur les indices recherchés.
Par défaut, SQL Server regroupe une table sur la clé primaire. La clé d'index cluster est utilisée pour identifier les lignes, elle est donc ajoutée en tant que colonne incluse à tous les autres index. Plus cette clé est large, plus chaque indice secondaire est grand.
Pire encore, si les index secondaires ne sont pas explicitement définis car
UNIQUE
la clé d'index clusterisée fait automatiquement partie de la clé de chacun d'entre eux. Cela s'applique généralement à la plupart des index, car les index ne sont généralement déclarés comme uniques que lorsque l'exigence est d'imposer l'unicité.Donc, si la question est, l'index groupé naturel contre substitut, le substitut gagnera presque toujours.
D'un autre côté, vous ajoutez cette colonne de substitution à la table, ce qui rend la table en soi plus grande. Cela rendra les analyses d'index en cluster plus coûteuses. Donc, si vous n'avez que très peu d'index secondaires et que votre charge de travail nécessite de regarder souvent toutes (ou la plupart des) lignes, vous pourriez en fait être mieux avec une clé naturelle qui enregistre ces quelques octets supplémentaires.
Enfin, les clés naturelles facilitent souvent la compréhension du modèle de données. Tout en utilisant plus d'espace de stockage, les clés primaires naturelles conduisent à des clés étrangères naturelles qui à leur tour augmentent la densité des informations locales.
Ainsi, comme si souvent dans le monde des bases de données, la vraie réponse est "ça dépend". Et - testez toujours dans votre propre environnement avec des données réalistes.
la source
Je crois que le meilleur se situe au milieu .
Présentation des touches naturelles:
CHAR(4)
etCHAR(20)
) économisent des octets supplémentaires, mais vous devez surveiller leur cohérence (ON UPDATE CASCADE
devient critique pour ces clés, qui pourraient être modifiées).Avantages: 1 et 2.
Watchouts: 3, 4 et 5.
Présentation des clés d'identité artificielles:
Vous n'avez pas à vous soucier de leur création et de leur gestion (dans la plupart des cas) car cette fonctionnalité est gérée par le moteur de base de données. Ils sont uniques par défaut et ne prennent pas beaucoup de place. Les opérations personnalisées comme
ON UPDATE CASCADE
peuvent être omises, car les valeurs de clé ne changent pas.Ils sont (souvent) les meilleurs candidats à la migration en tant que clés étrangères car:
2.1. se compose d'une colonne;
2.2. en utilisant un type simple qui a un petit poids et agit rapidement pour les opérations de comparaison.
Pour une association, dont les clés ne migrent nulle part, cela peut devenir une pure surcharge de données, car son utilité est perdue. La clé primaire naturelle complexe (s'il n'y a pas de colonnes de chaîne) sera plus utile.
Avantages: 1 et 2.
Attention: 3.
CONCLUSION:
Les clés Arificial sont plus faciles à entretenir, fiables et rapides car elles ont été conçues pour ces fonctionnalités. Mais dans certains cas, ne sont pas nécessaires. Par exemple, le
CHAR(4)
candidat à colonne unique se comporte dans la plupart des cas commeINT IDENTITY
. Il y a donc une autre question ici aussi: maintenabilité + stabilité ou évidence ?Question "Dois-je injecter une clé artificielle ou non?" dépend toujours de la structure de la clé naturelle:
la source
ON UPDATE CASCADE
non utilisé, alors que les clés n'ont jamais été mises à jour. Mais, s'ils le sont, cela pourrait être un problème s'ilON UPDATE NO ACTION
est configuré. Je veux dire, que le SGBD ne l'utilise jamais, alors que les valeurs des colonnes clés n'ont pas changé.Une clé est une caractéristique logique d'une base de données tandis que les performances sont toujours déterminées par l'implémentation physique dans le stockage et par les opérations physiques exécutées sur cette implémentation. C'est donc une erreur d'attribuer des caractéristiques de performance aux clés.
Cependant, dans cet exemple particulier, deux implémentations possibles de tables et de requêtes sont comparées. L'exemple ne répond pas à la question posée dans le titre ici. La comparaison est faite de jointures utilisant deux types de données différents (entier et caractère) en utilisant un seul type d'index (B-tree). Un point "évident" est que si un index de hachage ou un autre type d'index était utilisé, il n'y aurait probablement aucune différence de performance mesurable entre les deux implémentations. Il y a cependant des problèmes plus fondamentaux avec l'exemple.
Deux requêtes sont comparées pour les performances mais les deux requêtes ne sont pas logiquement équivalentes car elles renvoient des résultats différents! Un test plus réaliste comparerait deux requêtes renvoyant les mêmes résultats mais utilisant des implémentations différentes.
Le point essentiel à propos d'une clé de substitution est qu'il s'agit d'un attribut supplémentaire dans une table où la table a également des attributs de clé "significatifs" utilisés dans le domaine métier. Ce sont les attributs non substituts qui sont utiles pour que les résultats de la requête soient utiles. Un test réaliste comparerait donc les tables utilisant uniquement des clés naturelles avec une implémentation alternative ayant à la fois des clés naturelles et de substitution dans la même table. Les clés de substitution nécessitent généralement un stockage et une indexation supplémentaires et, par définition, nécessitent des contraintes d'unicité supplémentaires. Les mères porteuses nécessitent un traitement supplémentaire pour mapper les valeurs de clé naturelle externes sur leurs substituts et vice versa.
Comparez maintenant cette requête potentielle:
UNE.
À son équivalent logique si l'attribut NaturalTable1Key du tableau 2 est remplacé par l'IDTable1Key de substitution:
B.
La requête B nécessite une jointure; La requête A ne fonctionne pas. Il s'agit d'une situation familière dans les bases de données qui utilisent (sur) des substituts. Les requêtes deviennent inutilement complexes et beaucoup plus difficiles à optimiser. La logique métier (en particulier les contraintes d'intégrité des données) devient plus difficile à mettre en œuvre, à tester et à vérifier.
la source