À quoi sert la clé primaire comme dernière colonne d'un index secondaire composite dans une table InnoDB?

8

Dire que j'ai une relation 1-à-N (person_id, pet_id). J'ai une table où pet_idest la clé primaire.

Je comprends qu'un index secondaire InnoDB est essentiellement un arbre B où les valeurs sont les valeurs de clé primaire correspondantes pour la ligne.

Maintenant, supposons qu'une personne puisse avoir des milliers d'animaux de compagnie et je veux souvent les animaux de compagnie d'une personne dans l'ordre pet_id. Ensuite, il importerait que les enregistrements de l'index secondaire soient triés par (person_id, pet_id)ou simplement person_idavec les pet_id«pour» qui ne sont person_idpas triés. Deviner le plus tard.

Donc, si ce person_idn'est pas unique, les enregistrements sont-ils triés physiquement par (person_id, pet_id)ou JUSTE pet_id?

Merci

user3391564
la source
1
Je suppose que la dernière question est vraiment: "Donc, si ce person_idn'est pas unique, les enregistrements sont-ils triés physiquement par (person_id, pet_id)ou JUSTE person_id?"
ypercubeᵀᴹ

Réponses:

7

Non. Si votre table a le moteur InnoDB et le PRIMARY KEYis (pet_id), définissez un index secondaire comme (person_id)ou(person_id, pet_id) ne fait aucune différence.

L'index inclut également la pet_idcolonne afin que les valeurs soient triées comme (person_id, pet_id)dans les deux cas.

Une requête comme celle que vous avez:

SELECT pet_id FROM yourtable 
WHERE person_id = 127 
ORDER BY pet_id ;

devra accéder uniquement à l'index pour obtenir les valeurs et encore plus, il n'aura pas besoin de faire de tri, car les pet_idvaleurs sont déjà triées dans l'index. Vous pouvez le vérifier en regardant les plans d'exécution ( EXPLAIN):


Tout d'abord, nous essayons avec une table MyISAM:

 CREATE TABLE table pets 
 ( pet_id int not null auto_increment PRIMARY KEY, 
   person_id int not null, 
   INDEX person_ix (person_id)
 ) ENGINE = myisam ;

INSERT INTO pets (person_id) 
VALUES (1),(2),(3),(1),(2),(3),(4),(1),(8),(1),(2),(3) ;

mysql> EXPLAIN SELECT pet_id FROM pets 
               WHERE person_id = 2  
               ORDER BY pet_id asc \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: pets
         type: ref
possible_keys: person_ix
          key: person_ix
      key_len: 4
          ref: const
         rows: 3
        Extra: Using where; Using filesort
1 row in set (0.00 sec)

Remarquez le tri de fichiers!

Maintenant, MyISAM avec index composite:

 DROP TABLE IF EXISTS pets ;

 CREATE TABLE table pets 
 ( pet_id int not null auto_increment PRIMARY KEY, 
   person_id int not null, 
   INDEX person_ix (person_id, pet_id)            -- composite index
 ) ENGINE = myisam ;

INSERT INTO pets (person_id) 
VALUES (1),(2),(3),(1),(2),(3),(4),(1),(8),(1),(2),(3) ;


mysql> EXPLAIN SELECT pet_id FROM pets 
               WHERE person_id = 2  
               ORDER BY pet_id asc \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: pets
         type: ref
possible_keys: person_ix
          key: person_ix
      key_len: 4
          ref: const
         rows: 3
        Extra: Using where; Using index
1 row in set (0.00 sec)

Filesort a disparu , comme prévu.


Essayons maintenant la même chose avec le moteur InnoDB:

 DROP TABLE IF EXISTS pets ;

 CREATE TABLE table pets 
 ( pet_id int not null auto_increment PRIMARY KEY, 
   person_id int not null, 
   INDEX person_ix (person_id)            -- simple index
 ) ENGINE = innodb ;                      -- InnoDB engine

INSERT INTO pets (person_id) 
VALUES (1),(2),(3),(1),(2),(3),(4),(1),(8),(1),(2),(3) ;

mysql> EXPLAIN SELECT pet_id FROM pets 
               WHERE person_id = 2  
               ORDER BY pet_id asc \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: pets
         type: ref
possible_keys: person_ix
          key: person_ix
      key_len: 4
          ref: const
         rows: 3
        Extra: Using where; Using index
1 row in set (0.00 sec)

Pas de tri de fichiers non plus! Même si l'index n'a pas explicitement la pet_idcolonne, les valeurs sont là et triées. Vous pouvez vérifier que si vous définissez l'index avec (person_id, pet_id), leEXPLAIN est identique.

Permet de le faire, avec InnoDB et l'index composite:

 DROP TABLE IF EXISTS pets ;

 CREATE TABLE table pets 
 ( pet_id int not null auto_increment PRIMARY KEY, 
   person_id int not null, 
   INDEX person_ix (person_id, pet_id)    -- composite index
 ) ENGINE = innodb ;                      -- InnoDB engine

INSERT INTO pets (person_id) 
VALUES (1),(2),(3),(1),(2),(3),(4),(1),(8),(1),(2),(3) ;

mysql> EXPLAIN SELECT pet_id FROM pets 
               WHERE person_id = 2  
               ORDER BY pet_id asc \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: pets
         type: ref
possible_keys: person_ix
          key: person_ix
      key_len: 4
          ref: const
         rows: 3
        Extra: Using where; Using index
1 row in set (0.00 sec)

Plans identiques au cas précédent.


Pour être sûr à 100%, j'ai également exécuté les 2 derniers cas (moteur InnoDB, avec des index simples et composites) permettant le file_per_tableparamétrage et ajoutant quelques milliers de lignes dans le tableau:

DROP TABLE IF EXISTS ... ;
CREATE TABLE ... ;

mysql> INSERT INTO pets (person_id) 
       VALUES (1),(2),(3),(1),(2),(3),(4),(1),(8),(1),(2),(3) ;
Query OK, 12 rows affected (0.00 sec)
Records: 12  Duplicates: 0  Warnings: 0

mysql> INSERT INTO pets (person_id) 
       VALUES (1),(2),(3),(1),(2),(3),(4),(1),(8),(1),(2),(3),(127) ;
Query OK, 13 rows affected (0.00 sec)
Records: 13  Duplicates: 0  Warnings: 0

mysql> INSERT INTO pets (person_id) 
       VALUES (1),(2),(3),(1),(2),(3),(4),(1),(8),(1),(2),(3),(127) ;
Query OK, 13 rows affected (0.00 sec)
Records: 13  Duplicates: 0  Warnings: 0

mysql> INSERT INTO pets (person_id) 
       SELECT a.person_id+b.person_id-1 
       FROM pets a CROSS JOIN pets b CROSS JOIN pets c ;
Query OK, 54872 rows affected (0.47 sec)
Records: 54872  Duplicates: 0  Warnings: 0

Dans les deux cas, la vérification de la taille réelle des fichiers donne des résultats identiques :

ypercube@apollo:~$ sudo ls -la /var/lib/mysql/x/ | grep pets
-rw-rw----  1 mysql mysql     8604 Apr 21 07:25 pets.frm
-rw-rw----  1 mysql mysql 11534336 Apr 21 07:25 pets.ibd
ypercubeᵀᴹ
la source
1
En supposant qu'InnoDB fonctionne de manière similaire à cet égard avec MS SQL Server, il existe une différence entre un index sur (<some_column>)et (<some_column>, <pk>)parce qu'il ON (<some_column>)est équivalent à ON (<some_column>) INCLUDE (<pk>)et non ON (<some_column>, <pk>). Dans la plupart des cas, cela n'a pratiquement aucune signification, mais si votre PK est aléatoire (c'est-à-dire un UUID), cela ON (<s_c>,<pk>)peut entraîner une fragmentation supplémentaire ou si votre PK a un sens autre que d'être une clé et vous pourriez ORDER BY s_c, pkalors que ces tris seront plus rapides que l'index est déjà pleinement en ordre.
David Spillett
@DavidSpillett Droite. MySQL n'a cependant aucune INCLUDE (columns)fonctionnalité. C'est une autre raison pour laquelle j'ai conclu que l' (s_c)indice est équivalent à (s_c, pk).
ypercubeᵀᴹ
Je ne trouve pas de documentation pour me sauvegarder (donc je me souviens peut-être mal), mais je suis assez sûr d'avoir lu qu'InnoDB ne maintient pas le PK dans un ordre stable dans les index secondaires, sauf si on me le demande. Bien que la différence soit mineure de toute façon. Quand j'aurai le temps de jouer avec mySQL, je devrai tester la théorie ...
David Spillett
@DavidSpillett - blog.jcole.us/2013/01/10/… la section Index secondaires - "Il y a une chose à noter pour les pages non-feuilles de l'index secondaire: les champs clés en cluster (PKV) sont inclus dans l'enregistrement et sont considéré comme faisant partie de la clé de l'enregistrement, et non de sa valeur. " il les commande donc au moins au niveau des pages. Je ne sais pas exactement comment il se trouve dans une seule page de cette description, mais même s'ils ne le sont pas, cela est simplement résolu par un petit tampon - lisez les PK à partir d'une page, triez (max ~ 500? Articles) et récupérez ordonné, ce qui peut être hors du sujet.
jkavalik
2

Selon la documentation MySQL sur les index cluster et secondaire

Lien entre les index secondaires et l'index clusterisé

Tous les index autres que l'index cluster sont appelés index secondaires. Dans InnoDB, chaque enregistrement d'un index secondaire contient les colonnes de clé primaire de la ligne, ainsi que les colonnes spécifiées pour l'index secondaire . InnoDB utilise cette valeur de clé primaire pour rechercher la ligne dans l'index clusterisé.

Si la clé primaire est longue, les index secondaires utilisent plus d'espace, il est donc avantageux d'avoir une clé primaire courte.

Par conséquent, l'ajout de la CLÉ PRIMAIRE à un index secondaire est définitivement redondant. Votre entrée d'index voudrait (person_id, pet_id, pet_id). Cela alourdirait également inutilement l'index secondaire en ayant 2 copies du fichier PRIMARY KEY.

Pour l'index avec (person_id), si vous deviez exécuter une requête comme celle-ci

SELECT * FROM yourtable WHERE person_id = 127 ORDER BY pet_id;

Le PRIMARY KEYserait pleinement engagé dans cette requête et produirait de PRIMARY KEYtoute façon les résultats commandés par . D'un point de vue physique, les lignes sont classées par ordre d'insertion. Si le pet_id est AUTO_INCREMENT, alors il est ordonné par le numéro automatique.

RolandoMySQLDBA
la source
1
Afaik InnoDB ne "gonflera" pas l'index en ajoutant la colonne PK une deuxième fois lorsqu'elle est déjà présente. Vous pouvez même l'utiliser pour spécifier un ordre différent de colonnes PK pour la clé multicolonne: lorsque vous avez PK (owner_id, pet_id)mais vous pouvez créer une clé (vet_id, pet_id[, owner_id])pour utiliser un ordre de colonne différent.
jkavalik
2

Astuce 1:

PRIMARY KEY(x, id),
INDEX(id) -- where `id` is `AUTO_INCREMENT`

est parfaitement valable. Il présente l'avantage de performances d'être plus efficace lorsque de nombreuses requêtes doivent trouver plusieurs lignes WHERE x = 123. Autrement dit, il est légèrement plus efficace que le «évident»

PRIMARY KEY(id),
INDEX(x, id)

La seule règle concernant AUTO_INCREMENT(pour InnoDB) est qu'il iddoit s'agir de la première colonne d' un index. Notez que cette règle ne dit rien à propos de PRIMARYou UNIQUEou 'seulement la colonne'.

L'astuce est utile pour les énormes tables qui sont souvent récupérées xavec d'autres choses.

Astuce 2: Supposons que vous ayez

SELECT name FROM tbl WHERE person_id = 12 AND pet_id = 34;

Il s'agit d'un indice "couvrant":

INDEX(person_id, pet_id, name)

Autrement dit, la requête entière peut être effectuée à l'intérieur du BTree de l'index. L'EXPLAIN dira "Utilisation de l'index".

Rick James
la source