Cosa fa avere la chiave primaria come ultima colonna in un indice secondario composito in una tabella InnoDB?


8

Dire che ho un rapporto 1-a-N (person_id, pet_id). Ho una tabella in cui pet_idè la chiave primaria.

Comprendo che un indice secondario InnoDB è essenzialmente un albero B in cui i valori sono i corrispondenti valori della chiave primaria per la riga.

Ora, supponiamo che una persona possa avere migliaia di animali domestici e spesso voglio gli animali domestici di una persona in ordine di pet_id. Quindi importerebbe se i record nell'indice secondario sono ordinati per (person_id, pet_id)o semplicemente person_idcon quelli pet_idper quelli che person_idnon sono ordinati. Indovina il dopo.

Quindi, se person_idnon è univoco, i record sono ordinati fisicamente (person_id, pet_id)o SOLO pet_id?

Grazie


1
Suppongo che l'ultima domanda sia davvero: "Quindi, se person_idnon è univoco, i record sono ordinati fisicamente (person_id, pet_id)o SOLO person_id?"
ypercubeᵀᴹ

Risposte:


7

No. Se la tua tabella ha il motore InnoDB e lo PRIMARY KEYè (pet_id), allora la definizione di un indice secondario come (person_id)o (person_id, pet_id)non fa alcuna differenza.

L'indice include anche la pet_idcolonna, quindi i valori vengono ordinati come (person_id, pet_id)in entrambi i casi.

Una query come quella che hai:

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

dovrà accedere solo all'indice per ottenere i valori e anche di più, non dovrà fare alcun ordinamento, poiché i pet_idvalori sono già ordinati nell'indice. Puoi verificarlo guardando i piani di esecuzione ( EXPLAIN):


Innanzitutto, proviamo con una tabella 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)

Nota il filesort!

Ora, MyISAM con indice composito:

 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 è sparito , come previsto.


Ora proviamo lo stesso con il motore 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)

Nessun fileort neanche! Anche se l'indice non ha esplicitamente la pet_idcolonna, i valori sono presenti e ordinati. È possibile verificare che se si definisce l'indice con (person_id, pet_id), EXPLAINè identico.

Facciamolo, con InnoDB e l'indice composito:

 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)

Piani identici al caso precedente.


Per essere sicuro al 100%, eseguo anche gli ultimi 2 casi (motore InnoDB, con indici singoli e compositi) abilitando l' file_per_tableimpostazione e aggiungendo alcune migliaia di righe nella tabella:

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

In entrambi i casi, controllando le dimensioni effettive del file, si ottengono risultati identici :

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

1
Supponendo InnoDB funziona in modo simile a questo riguardo a MS SQL Server, non v'è una differenza tra un indice (<some_column>)e (<some_column>, <pk>)perché ON (<some_column>)equivale a ON (<some_column>) INCLUDE (<pk>)e non ON (<some_column>, <pk>). Nella maggior parte dei casi questo ha un significato praticamente pari a zero, ma se il tuo PK è casuale (cioè un UUID), allora ON (<s_c>,<pk>)può portare a una frammentazione aggiuntiva o se il tuo PK è significativo oltre ad essere una chiave e potresti ORDER BY s_c, pkquindi tali tipi sarà più veloce dell'indice è già completamente in ordine.
David Spillett,

@DavidSpillett Right. MySQL non ha INCLUDE (columns)funzionalità però. Questa è un'altra ragione per cui ho concluso che l' (s_c)indice è equivalente (s_c, pk).
ypercubeᵀᴹ

Non riesco a trovare la documentazione per eseguirne il backup (quindi potrei non ricordare) ma sono abbastanza sicuro di aver letto che InnoDB non mantiene il PK in ordine stabile negli indici secondari, a meno che non venga richiesto. Anche se la differenza è comunque minima. La prossima volta che avrò tempo di giocare con mySQL, dovrò testare la teoria ...
David Spillett,

@DavidSpillett - blog.jcole.us/2013/01/10/… la sezione degli indici secondari - "C'è una cosa da notare per le pagine non foglia dell'indice secondario: i campi chiave raggruppati (PKV) sono inclusi nel record ed è considerato parte della chiave del record, non del suo valore ". quindi li ordina almeno a livello di pagine. Non sono sicuro di come si trovi all'interno di una singola pagina da quella descrizione, ma anche se non lo sono, questo è semplicemente risolto da un piccolo buffer: leggi i PK da una pagina, ordina (max ~ 500? Articoli) e recupera gli ordini, quindi potrebbe essere irrilevante.
jkavalik,

2

Secondo la documentazione MySQL sugli indici cluster e secondari

Come gli indici secondari si collegano all'indice cluster

Tutti gli indici diversi dall'indice cluster sono noti come indici secondari. In InnoDB, ogni record in un indice secondario contiene le colonne chiave primaria per la riga, nonché le colonne specificate per l'indice secondario . InnoDB utilizza questo valore di chiave primaria per cercare la riga nell'indice cluster.

Se la chiave primaria è lunga, gli indici secondari utilizzano più spazio, quindi è vantaggioso disporre di una chiave primaria breve.

Pertanto, l'aggiunta del PRIMARY KEY a un indice secondario è decisamente ridondante. La tua voce di indice vorrebbe (person_id, pet_id, pet_id). Ciò gonfierebbe inutilmente anche l'indice secondario avendo 2 copie di PRIMARY KEY.

Per l'indice con (person_id), se si dovesse eseguire una query come questa

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

L' PRIMARY KEYsarebbero completamente impegnati in questa query e produce i risultati ordinate in PRIMARY KEYogni caso. Da un punto di vista fisico, le righe sono ordinate per ordine di inserzione. Se pet_id è AUTO_INCREMENT, viene ordinato dal numero automatico.


1
Afaik InnoDB non "gonfia" l'indice aggiungendo la colonna PK una seconda volta quando è già presente. Puoi anche usarlo per specificare un diverso ordine di colonne PK per chiave a più colonne: quando hai PK (owner_id, pet_id)ma puoi creare una chiave (vet_id, pet_id[, owner_id])per utilizzare un ordine di colonne diverso.
jkavalik,

2

Suggerimento 1:

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

è perfettamente valido. Ha il vantaggio in termini di prestazioni di essere più efficiente quando molte query devono trovare più righe WHERE x = 123. Cioè, è leggermente più efficiente dell'ovvio

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

L'unica regola su AUTO_INCREMENT(per InnoDB) è che iddeve essere la prima colonna in qualche indice. Nota che quella regola non dice nulla PRIMARYo UNIQUEo "solo colonna".

Il suggerimento è utile per enormi tavoli che vengono spesso recuperati xinsieme ad altre cose.

Suggerimento 2: Supponiamo di avere

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

Questo è un indice "coprente":

INDEX(person_id, pet_id, name)

Cioè, l'intera query può essere eseguita all'interno del BTree dell'indice. EXPLAIN dirà "Uso dell'indice".

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.