¿Qué significa tener la clave primaria como la última columna en un índice secundario compuesto en una tabla InnoDB?


8

Decir que tengo una relación 1-a-N (person_id, pet_id). Tengo una tabla donde pet_idestá la clave principal.

Entiendo que un índice secundario InnoDB es esencialmente un árbol B donde los valores son los valores de clave primaria correspondientes para la fila.

Ahora, supongamos que una persona puede tener miles de mascotas y, a menudo, quiero las mascotas de una persona en orden pet_id. Entonces sería importante si los registros en el índice secundario se ordenan por (person_id, pet_id)o solo person_idcon los pet_id's para eso person_idsin clasificar. Adivinando lo posterior.

Entonces, si person_idno es único, ¿los registros están ordenados físicamente (person_id, pet_id)o SOLO pet_id?

Gracias


1
Supongo que la última pregunta es realmente: "Entonces, si person_idno es único, ¿los registros están ordenados físicamente (person_id, pet_id)o SOLO person_id?"
ypercubeᵀᴹ

Respuestas:


7

No. Si su tabla tiene el motor InnoDB y PRIMARY KEYes (pet_id), entonces definir un índice secundario como (person_id)o (person_id, pet_id)no hace ninguna diferencia.

El índice también incluye la pet_idcolumna, por lo que los valores se ordenan como (person_id, pet_id)en ambos casos.

Una consulta como la que tienes:

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

necesitará acceder solo al índice para obtener los valores y aún más, no tendrá que hacer ningún tipo, ya que los pet_idvalores ya están ordenados en el índice. Puede verificar esto mirando los planes de ejecución ( EXPLAIN):


Primero, intentamos con una tabla 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)

¡Observe el clasificador de archivos!

Ahora, MyISAM con índice compuesto:

 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 se ha ido , como se esperaba.


Ahora intentemos lo mismo con el motor 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)

¡No hay clasificación de archivos tampoco! Aunque el índice no tiene explícitamente la pet_idcolumna, los valores están allí y ordenados. Puede verificar que si define el índice con (person_id, pet_id), el EXPLAINes idéntico.

Vamos a hacerlo, con InnoDB y el índice compuesto:

 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)

Planes idénticos al caso anterior.


Para estar 100% seguro, también ejecuto los últimos 2 casos (motor InnoDB, con índices únicos y compuestos) que permiten la file_per_tableconfiguración y agregan algunos miles de filas en la tabla:

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

En ambos casos, al verificar los tamaños de archivo reales, se obtienen resultados idénticos :

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
Suponiendo InnoDB funciona de manera similar a este respecto a MS SQL Server, no es una diferencia entre un índice en (<some_column>)y (<some_column>, <pk>)porque ON (<some_column>)es equivalente a ON (<some_column>) INCLUDE (<pk>)no ON (<some_column>, <pk>). En la mayoría de los casos, esto tiene una importancia prácticamente nula, pero si su PK es aleatoria (es decir, un UUID), ON (<s_c>,<pk>)puede generar una fragmentación adicional o si su PK es significativa además de ser una clave y podría ORDER BY s_c, pkser que ese tipo será más rápido como índice Ya está completamente en orden.
David Spillett

@DavidSpillett Derecha. Sin INCLUDE (columns)embargo, MySQL no tiene funcionalidad. Esa es otra razón por la que concluí que el (s_c)índice es equivalente a (s_c, pk).
ypercubeᵀᴹ

No puedo encontrar documentación que me respalde (por lo que puedo estar recordando mal) pero estoy bastante seguro de que he leído que InnoDB no mantiene la PK en orden estable en los índices secundarios a menos que se lo pida. Aunque la diferencia es menor de todos modos. La próxima vez que tenga tiempo para jugar con mySQL tendré que probar la teoría ...
David Spillett

@DavidSpillett - blog.jcole.us/2013/01/10/… la sección de índices secundarios - "Hay una cosa importante para las páginas secundarias de índice secundario: los campos clave agrupados (PKV) se incluyen en el registro y considerado parte de la clave del registro, no su valor ". así que los ordena como mínimo en el nivel de páginas. No estoy seguro exactamente cómo está dentro de una sola página de esa descripción, pero incluso si no lo están, eso simplemente se resuelve con un pequeño búfer: lea las PK de una página, ordene (¿máximo ~ 500? Artículos) y busque ordenado, por lo que puede ser irrelevante.
jkavalik

2

De acuerdo con la documentación de MySQL sobre los índices agrupados y secundarios

Cómo se relacionan los índices secundarios con el índice agrupado

Todos los índices que no sean el índice agrupado se conocen como índices secundarios. En InnoDB, cada registro en un índice secundario contiene las columnas de clave principal para la fila, así como las columnas especificadas para el índice secundario . InnoDB usa este valor de clave principal para buscar la fila en el índice agrupado.

Si la clave primaria es larga, los índices secundarios usan más espacio, por lo que es ventajoso tener una clave primaria corta.

Por lo tanto, agregar la CLAVE PRIMARIA a un índice secundario es definitivamente redundante. Su entrada de índice le gustaría (person_id, pet_id, pet_id). Esto también hincharía innecesariamente el índice secundario al tener 2 copias de PRIMARY KEY.

Para el índice con (person_id), si tuviera que ejecutar una consulta como esta

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

El PRIMARY KEYestaría completamente involucrado en esta consulta y produce los resultados ordenados de PRIMARY KEYtodos modos. Desde un punto de vista físico, las filas están ordenadas por orden de inserción. Si pet_id es AUTO_INCREMENT, entonces se ordena por el número automático.


1
Afaik InnoDB no "hinchará" el índice agregando la columna PK por segunda vez cuando ya está presente. Incluso puede usarlo para especificar un orden diferente de columnas PK para la clave de varias columnas: cuando tiene PK (owner_id, pet_id)pero puede crear una clave (vet_id, pet_id[, owner_id])para utilizar un orden de columna diferente.
jkavalik

2

Consejo 1:

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

Es perfectamente válido. Tiene la ventaja de rendimiento de ser más eficiente cuando muchas consultas necesitan encontrar varias filas WHERE x = 123. Es decir, es un poco más eficiente que lo 'obvio'

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

La única regla sobre AUTO_INCREMENT(para InnoDB) es que iddebe ser la primera columna de algún índice. Tenga en cuenta que esta regla no dice nada sobre PRIMARYo UNIQUEo 'única columna'.

La sugerencia es útil para tablas enormes que a menudo se obtienen xjunto con otras cosas.

Consejo 2: suponga que tiene

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

Este es un índice de "cobertura":

INDEX(person_id, pet_id, name)

Es decir, toda la consulta se puede hacer dentro del índice BTree. El EXPLICAR dirá "Usando índice".

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.