Что делает наличие первичного ключа в качестве последнего столбца в составном вторичном индексе в таблице InnoDB?

8

Скажем , у меня есть отношения 1-к-N (person_id, pet_id). У меня есть таблица, где pet_idнаходится первичный ключ.

Я понимаю, что вторичный индекс InnoDB - это, по сути, B-дерево, где значения являются соответствующими значениями первичного ключа для строки.

Теперь предположим, что у одного человека могут быть тысячи домашних животных, и я часто хочу, чтобы домашние животные человека были в порядке pet_id. Тогда будет иметь значение, будут ли записи во вторичном индексе отсортированы по (person_id, pet_id)или просто person_idс тем pet_id, что person_idне отсортировано . Догадываюсь позже.

Итак, если person_idзапись не уникальна, физически отсортированы записи (person_id, pet_id)или просто pet_id?

Спасибо

user3391564
источник
1
Я полагаю, что последний вопрос действительно таков: «Итак, если записи person_idне уникальны, физически отсортированы записи (person_id, pet_id)или просто person_id
ypercubeᵀᴹ

Ответы:

7

Нет. Если ваша таблица имеет механизм InnoDB и имеет значение PRIMARY KEYis (pet_id), то определение вторичного индекса как (person_id)или (person_id, pet_id)не имеет значения.

Индекс также включает pet_idстолбец, поэтому значения сортируются как (person_id, pet_id)в обоих случаях.

Запрос, подобный тому, который у вас есть:

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

для получения значений потребуется доступ только к индексу, и даже больше, для этого не нужно выполнять никакой сортировки, поскольку pet_idзначения уже отсортированы в индексе. Вы можете убедиться в этом, посмотрев планы выполнения ( EXPLAIN):


Сначала мы попробуем с таблицей 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)

Обратите внимание на сортировку файлов!

Теперь MyISAM с составным индексом:

 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)

Файловая сортировка прошла , как и ожидалось.


Теперь давайте попробуем то же самое с движком 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)

Также нет сортировки файлов! Несмотря на то, что индекс не имеет явно pet_idстолбца, значения там и отсортированы. Вы можете проверить, что если вы определяете индекс с помощью (person_id, pet_id), EXPLAINон идентичен.

Давайте на самом деле сделаем это, используя 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, 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)

Идентичные планы с предыдущим делом.


Чтобы быть на 100% уверенным, я также запускаю последние 2 случая (механизм InnoDB, с единичными и составными индексами), включив file_per_tableнастройку и добавив несколько тысяч строк в таблицу:

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

В обоих случаях проверка фактических размеров файлов дает одинаковые результаты :

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ᵀᴹ
источник
1
Предполагая , что InnoDB работает так же в этом отношении к MS SQL Server, то есть разница между индексом на (<some_column>)и (<some_column>, <pk>)потому , что ON (<some_column>)эквивалентно , ON (<some_column>) INCLUDE (<pk>)а не ON (<some_column>, <pk>). В большинстве случаев это имеет практически нулевое значение, но если ваш PK является случайным (то есть UUID), то это ON (<s_c>,<pk>)может привести к дополнительной фрагментации или если ваш PK имеет значение, отличное от того, чтобы быть ключом, и вы могли бы ORDER BY s_c, pkтогда такие сортировки быть быстрее, чем индекс уже полностью в порядке.
Дэвид Спиллетт
@DavidSpillett Верно. MySQL не имеет INCLUDE (columns)функциональности, хотя. Это еще одна причина, по которой я пришел к выводу, что (s_c)индекс эквивалентен (s_c, pk).
ypercubeᵀᴹ
Я не могу найти документацию, чтобы поддержать меня (так что, возможно, я запомнил), но я вполне уверен, что прочитал, что InnoDB не поддерживает PK в стабильном порядке во вторичных индексах, если не попросить об этом. Хотя разница в любом случае незначительна. Когда у меня будет время поиграть с MySQL, мне придется проверить теорию ...
Дэвид Спиллетт
@DavidSpillett - blog.jcole.us/2013/01/10/… Раздел « Вторичные индексы »: «Для нестраничных страниц вторичного индекса есть одна вещь, на которую следует обратить внимание: поля кластерных ключей (PKV) включены в запись и считается частью ключа записи, а не его значением ". поэтому он упорядочивает их как минимум на уровне страниц. Не уверен точно, как это находится внутри одной страницы из этого описания, но даже если это не так, это просто решается небольшим буфером - считывание PK с одной страницы, сортировка (макс. ~ 500? Элементов) и выборка, упорядоченная так, что может быть не имеет значения.
jkavalik
2

Согласно документации MySQL по кластерным и вторичным индексам

Как вторичные индексы связаны с кластерным индексом

Все индексы, кроме кластеризованного, называются вторичными. В InnoDB каждая запись во вторичном индексе содержит столбцы первичного ключа для строки, а также столбцы, указанные для вторичного индекса . InnoDB использует это значение первичного ключа для поиска строки в кластерном индексе.

Если первичный ключ длинный, вторичные индексы используют больше места, поэтому выгодно иметь короткий первичный ключ.

Следовательно, добавление PRIMARY KEY к вторичному индексу однозначно избыточно. Ваша индексная запись хотела бы (person_id, pet_id, pet_id). Это также излишне раздувает вторичный индекс, имея 2 копии PRIMARY KEY.

Для индекса с (person_id), если вы должны были выполнить запрос, как это

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

Он PRIMARY KEYбудет полностью вовлечен в этот запрос и выдаст результаты, упорядоченные в PRIMARY KEYлюбом случае. С физической точки зрения строки упорядочены по порядку вставки. Если pet_id это AUTO_INCREMENT, то это порядок по номеру авто.

RolandoMySQLDBA
источник
1
Afaik InnoDB не будет «раздувать» индекс, добавляя столбец PK во второй раз, когда он уже присутствует. Вы даже можете использовать его, чтобы указать другой порядок столбцов PK для многоколоночного ключа: если у вас есть PK, (owner_id, pet_id)но вы можете создать ключ (vet_id, pet_id[, owner_id])для использования другого порядка столбцов.
jkavalik
2

Совет 1:

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

совершенно верно. Он имеет преимущество в производительности, заключающееся в большей эффективности, когда во многих запросах необходимо найти несколько строк WHERE x = 123. То есть он немного более эффективен, чем «очевидный»

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

Единственное правило о AUTO_INCREMENT(для InnoDB) заключается в том, что он idдолжен быть первым столбцом в некотором индексе. Обратите внимание, что это правило ничего не говорит о PRIMARYили UNIQUEили «только столбец».

Совет полезен для огромных таблиц, которые часто выбираются xвместе с другими вещами.

Совет 2: Предположим, у вас есть

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

Это индекс «покрытия»:

INDEX(person_id, pet_id, name)

То есть весь запрос может быть выполнен внутри индекса BTree. Объяснение скажет "Использование индекса".

Рик Джеймс
источник