web-dev-qa-db-fra.com

Auto-incrémentation vs clé primaire composite pour une grande table sur innodb

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?

6
user8012596

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.

8
joanolo

joanolo a quelques bons points, et certains points avec lesquels je ne suis pas d'accord ...

  • Depuis 5.6.4, DATETIME et TIMESTAMP, sans fraction de seconde, prennent chacun 5 octets. (Donc, le PK en question est un total de 9 octets.)
  • La fragmentation des données n'est pas si mauvaise. Et, s'il permet une amélioration significative d'autres actions, cela peut valoir la peine. (Voir ci-dessous.) Un BTree s'installe intrinsèquement à environ 69%. (Un bloc divisé transforme un bloc complet à 100% en deux blocs complets à 50%, puis ils se remplissent tous les deux progressivement.)
  • L'utilisation d'un 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.)
  • Le lien sur les PK parle des PK "gras". Le PK en question n'est que de 9 octets - pas vraiment gros. Le lien n'est donc que légèrement pertinent. De plus, le gras ne s'applique que si vous avez au moins 2 index secondaires qui n'incluent pas les colonnes de gras.
  • Le tableau menace de déborder d'un 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).
  • Je suis presque sûr qu'il y a non "alignement" des champs dans les structures InnoDB. InnoDB est conçu de telle sorte que les fichiers sont compatibles avec toutes les architectures matérielles.
  • MySQL nécessite que le PK soit unique. Si l'abandon de alarmTypeID supprime l'unicité, ne le faites pas!

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

4
Rick James