在InnoDB表中,将主键作为复合二级索引的最后一列有什么作用?


8

假设我有一对一的关系(person_id, pet_id)。我有一个表在哪里pet_id是主键。

我了解InnoDB二级索引实际上是一个B树,其中的值是该行的相应主键值。

现在,假设一个人可以养成千上万只宠物,而我经常想要一个人的宠物按顺序排列pet_id。那么,如果在第二个索引记录的排序会的问题(person_id, pet_id)或只是person_idpet_id的该person_id是无序。猜后来。

因此,如果person_id是非唯一的,记录是按(person_id, pet_id)还是JUST 物理排序的pet_id

谢谢


1
我想最后一个问题确实是:“因此,如果person_id不是唯一的,记录是按物理方式(person_id, pet_id)还是按逻辑排序person_id?”
ypercubeᵀᴹ

Answers:


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)

Filesort消失了,正如预期的那样。


现在让我们使用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%的确定,我还运行了后两种情况(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

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的索引排序可能会更快已经完全正常了。
David Spillett

@DavidSpillett对。MySQL没有INCLUDE (columns)功能。这是我得出的结论(s_c)等于的另一个原因(s_c, pk)
ypercubeᵀᴹ

我找不到文档来支持我(因此我可能记错了),但我可以肯定地说,我已经读到InnoDB不会在二级索引中保持PK的稳定顺序,除非要求这样做。尽管两者之间的差异很小。当我下次有时间使用mySQL时,我将不得不测试该理论……
David Spillett

@DavidSpillett - blog.jcole.us/2013/01/10/...辅助索引部分- “有记为辅助索引非叶子页的一两件事:聚集键字段(PKV)被列入记录,并被视为记录密钥的一部分,而不是其值。” 因此它至少在页面级别上对它们进行排序。不确定从该描述中确切知道它在单个页面中的位置如何,但是即使不是,也可以通过一个小的缓冲区来解决-从一页中读取PK,排序(最多〜500个项目)并按顺序进行获取不相关的。
jkavalik '16

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,则按自动编号排序。


1
Afaik InnoDB不会通过在已经存在时第二次添加PK列来“膨胀”索引。您甚至可以使用它为多列键指定不同的PK列顺序:当您拥有PK (owner_id, pet_id)但可以创建键(vet_id, pet_id[, owner_id])以利用不同的列顺序时。
jkavalik '16

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必须是某个索引的第一列。请注意,该规则未对“ 或” 或“仅列”进行说明。PRIMARYUNIQUE

提示对于通常x与其他内容一起获取的大型表很有用。

提示2:假设您有

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

这是一个“覆盖”索引:

INDEX(person_id, pet_id, name)

也就是说,整个查询可以在索引的BTree中完成。EXPLAIN会说“正在使用索引”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.