Was bedeutet es, den Primärschlüssel als letzte Spalte in einem zusammengesetzten Sekundärindex in einer InnoDB-Tabelle zu haben?


8

Angenommen, ich habe eine 1-zu-N-Beziehung (person_id, pet_id). Ich habe eine Tabelle, in der pet_idsich der Primärschlüssel befindet.

Ich verstehe, dass ein InnoDB-Sekundärindex im Wesentlichen ein B-Baum ist, in dem die Werte die entsprechenden Primärschlüsselwerte für die Zeile sind.

Angenommen, eine Person kann Tausende von Haustieren haben, und ich möchte oft die Haustiere einer Person in der Reihenfolge von pet_id. Dann wäre es wichtig, ob Datensätze im Sekundärindex nach (person_id, pet_id)oder nur person_idmit den pet_id's sortiert werden , person_iddie unsortiert sind. Ich rate das später.

Wenn dies person_idnicht eindeutig ist, werden Datensätze physisch nach (person_id, pet_id)oder NUR sortiert pet_id?

Vielen Dank


1
Ich nehme an, die letzte Frage lautet wirklich: "Wenn sie person_idnicht eindeutig sind, werden Datensätze physisch nach (person_id, pet_id)oder NUR sortiert person_id?"
Ypercubeᵀᴹ

Antworten:


7

Nein. Wenn Ihre Tabelle über die InnoDB-Engine und die PRIMARY KEYis verfügt (pet_id), definiert die Definition eines Sekundärindex keinen (person_id)oder (person_id, pet_id)keinen Unterschied.

Der Index enthält auch die pet_idSpalte, sodass die Werte wie (person_id, pet_id)in beiden Fällen sortiert sind.

Eine Abfrage wie die, die Sie haben:

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

muss nur auf den Index zugreifen, um die Werte zu erhalten, und noch mehr, es muss keine Sortierung durchgeführt werden, da die pet_idWerte bereits im Index sortiert sind. Sie können dies überprüfen, indem Sie sich die Ausführungspläne ( EXPLAIN) ansehen :


Zuerst versuchen wir es mit einer MyISAM-Tabelle:

 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)

Beachten Sie die Dateisortierung!

Jetzt MyISAM mit zusammengesetztem Index:

 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 ist wie erwartet weg .


Versuchen wir jetzt dasselbe mit der InnoDB-Engine:

 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)

Auch kein Dateisort! Obwohl der Index die pet_idSpalte nicht explizit enthält, sind die Werte vorhanden und sortiert. Sie können überprüfen , dass , wenn Sie den Index mit definieren (person_id, pet_id), die EXPLAINidentisch ist.

Machen wir es tatsächlich mit InnoDB und dem zusammengesetzten Index:

 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)

Identische Pläne mit dem vorherigen Fall.


Um 100% sicher zu sein, führe ich auch die letzten beiden Fälle aus (InnoDB-Engine mit einzelnen und zusammengesetzten Indizes), um die file_per_tableEinstellung zu ermöglichen und der Tabelle einige tausend Zeilen hinzuzufügen:

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 beiden Fällen führt die Überprüfung der tatsächlichen Dateigrößen zu identischen Ergebnissen :

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
Angenommen, InnoDB funktioniert in dieser Hinsicht ähnlich wie MS SQL Server, gibt es einen Unterschied zwischen einem Index für (<some_column>)und (<some_column>, <pk>)weil ON (<some_column>)äquivalent zu ON (<some_column>) INCLUDE (<pk>)und nicht ON (<some_column>, <pk>). In den meisten Fällen hat dies so ziemlich Null Bedeutung, aber wenn Ihr PK zufällig ist (dh ein UUID) , dann ON (<s_c>,<pk>)können zusätzliche Fragmentierung führen oder wenn die PK andere sinnvoll ist , als ein Schlüssel zu sein und man könnte ORDER BY s_c, pkdann wird eine solche Art schneller als der Index ist schon voll in ordnung.
David Spillett

@ DavidSpillett Richtig. MySQL hat jedoch keine INCLUDE (columns)Funktionalität. Das ist ein weiterer Grund, warum ich zu dem Schluss gekommen bin, dass der (s_c)Index äquivalent zu ist (s_c, pk).
Ypercubeᵀᴹ

Ich kann keine Dokumentation finden, die mich sichert (daher erinnere ich mich möglicherweise falsch), aber ich bin mir ziemlich sicher, dass ich gelesen habe, dass InnoDB die PK in Sekundärindizes nicht in stabiler Reihenfolge hält, es sei denn, Sie werden dazu aufgefordert. Obwohl der Unterschied sowieso gering ist. Wenn ich das nächste Mal Zeit habe, mit mySQL zu spielen, muss ich die Theorie testen ...
David Spillett

@DavidSpillett - blog.jcole.us/2013/01/10/… der Abschnitt Sekundärindizes - "Für Seiten ohne Sekundärindex gibt es eine Anmerkung: Die Clustered Key Fields (PKV) sind im Datensatz enthalten und sind wird als Teil des Schlüssels des Datensatzes betrachtet, nicht als dessen Wert. " so ordnet es sie mindestens auf der Ebene der Seiten. Ich bin mir nicht sicher, wie genau es sich innerhalb einer einzelnen Seite aus dieser Beschreibung befindet, aber selbst wenn dies nicht der Fall ist, wird dies einfach durch einen kleinen Puffer gelöst - lesen Sie die PKs von einer Seite, sortieren Sie sie (max. ~ 500? Elemente) und rufen Sie sie ab irrelevant.
Jkavalik

2

Gemäß der MySQL-Dokumentation zu den Cluster- und Sekundärindizes

Wie sich Sekundärindizes auf den Clustered-Index beziehen

Alle Indizes außer dem Clustered-Index werden als Sekundärindizes bezeichnet. In InnoDB enthält jeder Datensatz in einem Sekundärindex die Primärschlüsselspalten für die Zeile sowie die für den Sekundärindex angegebenen Spalten . InnoDB verwendet diesen Primärschlüsselwert, um nach der Zeile im Clustered-Index zu suchen.

Wenn der Primärschlüssel lang ist, belegen die Sekundärindizes mehr Speicherplatz. Daher ist es vorteilhaft, einen kurzen Primärschlüssel zu haben.

Daher ist das Hinzufügen des PRIMARY KEY zu einem Sekundärindex definitiv redundant. Ihr Indexeintrag möchte (person_id, pet_id, pet_id). Dies würde auch den Sekundärindex unnötig aufblähen, indem 2 Kopien des PRIMARY KEY.

Für den Index mit (person_id), wenn Sie eine Abfrage wie diese ausführen würden

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

Der PRIMARY KEYwürde sich voll und ganz mit dieser Abfrage beschäftigen und die von PRIMARY KEYohnehin geordneten Ergebnisse erzeugen . Aus physikalischer Sicht sind die Zeilen nach Einfügereihenfolge geordnet. Wenn die pet_id AUTO_INCREMENT ist, wird sie nach der automatischen Nummer sortiert.


1
Afaik InnoDB wird den Index nicht "aufblähen", indem die PK-Spalte zum zweiten Mal hinzugefügt wird, wenn sie bereits vorhanden ist. Sie können damit sogar eine andere Reihenfolge von PK-Spalten für mehrspaltige Schlüssel angeben: Wenn Sie über PK verfügen, (owner_id, pet_id)aber einen Schlüssel erstellen können, um eine (vet_id, pet_id[, owner_id])andere Spaltenreihenfolge zu verwenden.
Jkavalik

2

Tipp 1:

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

ist vollkommen gültig. Es hat den Leistungsvorteil, effizienter zu sein, wenn viele Abfragen mehrere Zeilen finden müssen WHERE x = 123. Das heißt, es ist etwas effizienter als das "Offensichtliche"

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

Die einzige Regel AUTO_INCREMENT(für InnoDB) ist, iddass die erste Spalte in einem Index sein muss. Beachten Sie, dass diese Regel nichts über PRIMARYoder UNIQUEoder "nur Spalte" sagt .

Der Tipp ist nützlich für große Tische, die oft xzusammen mit anderen Dingen abgerufen werden .

Tipp 2: Angenommen, Sie haben

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

Dies ist ein "abdeckender" Index:

INDEX(person_id, pet_id, name)

Das heißt, die gesamte Abfrage kann im BTree des Index durchgeführt werden. Das EXPLAIN sagt "Using index".

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.