J'ai eu une discussion assez longue avec Rick James à ce sujet, nous avons eu l'idée d'avoir une clé composite pour remplacer le pk d'auto-incrémentation où l'int est limité à près de 2 milliards. Ma table atteindra facilement cette limite en quelques mois car chaque mois, nous capturons près de quelques centaines de millions de données. Voici à quoi ressemble ma table. Le tableau des clés est le gdata
donc j'ai composé le primaire en utilisant 3 champs PRIMARY KEY (alarmTypeID,vehicleID,gDateTime)
. Ensuite, j'ai une autre table appelée table d'alarme. Le lien entre les deux est un à plusieurs. Cela signifie qu'une donnée dans gdata
peut avoir zéro ou plusieurs alarms
qui lui sont liées. Le lien entre eux est vehicleID
et gDateTime
.
CREATE TABLE `gdata` (
`alarmTypeID` tinyint(4) NOT NULL DEFAULT '0',
`fleetID` smallint(11) NOT NULL,
`fleetGroupID` smallint(11) DEFAULT NULL,
`fleetSubGroupID` smallint(11) DEFAULT NULL,
`deviceID` mediumint(11) NOT NULL,
`vehicleID` mediumint(11) NOT NULL,
`gDateTime` datetime NOT NULL,
`insertDateTime` datetime NOT NULL,
`latitude` float NOT NULL,
`longitude` float NOT NULL,
`speed` smallint(11) NOT NULL
-- (see full text)
) ;
ALTER TABLE `gdata`
ADD PRIMARY KEY (`alarmTypeID`,`vehicleID`,`gDateTime`),
ADD KEY `gDateTime` (`gDateTime`),
ADD KEY `fleetID` (`fleetID`,`vehicleID`,`gDateTime`);
COMMIT;
Voici le tableau des alarmes
CREATE TABLE `alarm` (
`alarmTypeID` tinyint(4) NOT NULL,
`vehicleID` mediumint(9) NOT NULL,
`gDateTime` datetime NOT NULL,
`insertDateTime` datetime NOT NULL,
`alarmValue` varchar(5) NOT NULL,
`readWeb` enum('n','y') NOT NULL DEFAULT 'n',
`readWebDateTime` datetime NOT NULL,
`readMobile` enum('n','y') NOT NULL DEFAULT 'n',
`readMobileDateTim` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `alarm`
ADD PRIMARY KEY (`alarmTypeID`,`vehicleID`,`gDateTime`);
COMMIT;
Tout a l'air bien, mais récemment, je faisais des recherches sur un sujet connexe et j'ai constaté que certaines discussions https://www.quora.com/Is-it-a-bad-idea-to-have-a-primary- key-on-3-or-more-columns sont contre la clé primaire composite et préfèrent utiliser l'auto-incrément principalement à des fins d'insertion. Quelqu'un peut-il faire la lumière sur ce point pour conserver la clé composite du primaire ou revenir à l'auto-incrémentation?
Il n'y a rien de mal avec une clé composite. Cependant, vous devez prendre en compte comment InnoDB
stocke les données .
Citant la documentation liée ci-dessus:
Les données de chaque table InnoDB sont divisées en pages. Les pages qui composent chaque table sont organisées dans une structure de données arborescente appelée index B-tree. Les données de table et les index secondaires utilisent tous deux ce type de structure. L'index B-tree qui représente une table entière est connu sous le nom d'index cluster , qui est organisé selon le colonnes de clé primaire . Les nœuds de la structure de données d'index contiennent les valeurs de toutes les colonnes de cette ligne (pour l'index cluster) ou les colonnes d'index et les colonnes de clé primaire (pour les index secondaires).
Autrement dit, InnoDB stockera les données en fonction de votre PRIMARY KEY
. Si les données que vous insérez ont un PK croissant, la fragmentation de page ne se produit pas. Cela se produira toujours avec un AUTO_INCREMENT
. Si vous insérez les données dans ordre chronologique (c.-à-d. gDateTime
augmente toujours de façon monotone), changez ordre des colonnes qui font votre PK pour:
PRIMARY KEY (`gDateTime`, `alarmTypeID`, `vehicleID`)
... aura les mêmes avantages en ce qui concerne le fait de ne pas avoir à "ajuster une nouvelle ligne au milieu des autres" (ce qui signifie que l'arbre B n'est pas fragmenté pour chaque insert).
Cependant: si vous référencez cette table à partir d'autres tables (liées), vous devez stocker, dans la table de référence, toujours le PK (gDateTime
, alarmTypeID
, vehicleID
). Cela signifie que vous économisez à chaque fois 7 ou 8 octets de stockage. Le PK composite utiliserait 2 + 1 + 8 = 11 octets d'informations (probablement il utilise 12 octets en raison de l'alignement); alors qu'un INT UNSIGNED AUTO_INCREMENT
, vous n'utiliserez que 4 octets dans la table de référencement. Vous êtes limité à 2 ^ 32 valeurs différentes pour votre PK. Si vous avez besoin de plus de 2 ^ 32 valeurs, vous aurez besoin de BIGINT AUTO_INCREMENT
, cela vous donne 2 ^ 64 (et je n'ai pas encore trouvé de cas pratique où ce ne soit pas assez grand).
Que cela ait du sens ou non, cela dépend beaucoup de votre scénario particulier.
joanolo a quelques bons points, et certains points avec lesquels je ne suis pas d'accord ...
DATETIME
et TIMESTAMP
, sans fraction de seconde, prennent chacun 5 octets. (Donc, le PK en question est un total de 9 octets.)DATETIME
(ou TIMESTAMP
) dans une clé PRIMARY
ou UNIQUE
est dangereuse - et si deux entrées se produisent exactement en même temps? (Cette question dépend de l'application; par exemple, mesurer l'emplacement d'un camion n'a pas besoin de deux lectures GPS en une seconde.)INT
de 4 octets, le choix suivant pour AUTO_INCREMENT
est un BIGINT
de 8 octets; pas très différent de 9 octets.MEDIUMINT
est de 3 octets (vehicleID
).Détails...
ADD PRIMARY KEY (`alarmTypeID`,`vehicleID`,`gDateTime`), -- 1+3+5 = 0 bytes
ADD KEY `gDateTime` (`gDateTime`), -- 5 + 1+3 = 9
ADD KEY `fleetID` (`fleetID`,`vehicleID`,`gDateTime`); -- 2+3+5 + 1 = 11
Je dis 0 octet pour le PK car il est inclus avec le reste des colonnes. Les nombres pour les clés secondaires sont les tailles des colonnes de clés secondaires + colonnes PK supplémentaires. (Il y a, bien sûr, des frais généraux importants dans un index, donc ces chiffres ne peuvent pas être utilisés pour calculer la taille ultime du BTree. Vous pourriez avoir besoin d'un facteur de fudge de 3x.)
Un SELECT
avec
WHERE alarmTypeID = constant
AND vehicleID = constant
AND gDateTime ... (some range)
est bien mieux géré par (alarmTypeID
, vehicleID
, gDateTime
) que par (gDateTime
, alarmTypeID
, vehicleID
). S'il s'agit d'une requête courante, je soutiens qu'elle l'emporte sur le désir d'éviter la fragmentation.
CLÉ PRIMAIRE (alarmTypeID
, vehicleID
, gDateTime
) évite le rebond entre la clé secondaire et les données.
LA CLÉ PRIMAIRE (gDateTime
, alarmTypeID
, vehicleID
) ne peut pas utiliser d'alarme ou de véhicule, et devrait passer par-dessus les alarmes et les véhicules qui ne sont pas d'intérêt. Ou utilisez une clé secondaire, ce qui entraîne des rebonds d'avant en arrière. Dans les deux cas, beaucoup plus lent. (Règle générale: 10 fois plus lent pour la rotation des disques lorsque les données ne sont pas mises en cache.)