mysql聚集索引的优缺点

时间:2020-12-20 06:28:25

  聚簇索引并不是一种单独的索引类型,而是一种数据存储方式(不是数据结构,而是存储结构),具体细节依赖于其实现方式,但innodb的聚簇索引实际上是在同一个结构中保存了btree索引和数据行。

  当表有索引时,它的数据行实际上存放在索引的叶子页中,属于聚簇表示数据行和相邻的键值紧凑地存储在一起,因为无法同时把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。因为是存储引擎负责实现索引,因此不是所有的存储引擎都支持聚簇索引。下面主要介绍innodb,但下面讨论的原理对于任何支持聚簇索引的引擎都适用:

  叶子页包含了行的全部数据,但是节点页只包含了索引列(或者可以说非叶子节点的节点页包含的是索引值的索引,因为这些节点页包含的值是从索引列中提取出来的)。

  innodb将通过主键聚集数据,如果没有定义主键,Innodb会选择第一个非空的唯一索引代替,如果没有非空唯一索引,Innodb会隐式定义一个6字节的rowid主键来作为聚集索引。innodb只聚集在同一个页面中的记录,包含相邻键值的页面可能会相距甚远。

  要注意:聚簇主键可能对性能有帮助,但也可能导致严重的性能问题,尤其是将表的存储引擎从innodb转换成其他引擎的时候。

聚集的数据有一些重要的优点:

  A:可以把相关数据保存在一起,如:实现电子邮箱时,可以根据用户ID来聚集数据,这样只需要从磁盘读取少量的数据页就能获取某个用户全部邮件,如果没有使用聚集索引,则每封邮件都可能导致一次磁盘IO

  B:数据访问更快,聚集索引将索引和数据保存在同一个btree中,因此从聚集索引中获取数据通常比在非聚集索引中查找要快

  C:使用覆盖索引扫描的查询可以直接使用页节点中的主键值

聚集索引的缺点:

  A:聚簇数据最大限度地提高了IO密集型应用的性能,但如果数据全部放在内存中,则访问的顺序就没有那么重要了,聚集索引也没有什么优势了

  B:插入速度严重依赖于插入顺序,按照主键的顺序插入是加载数据到innodb表中速度最快的方式,但如果不是按照主键顺序加载数据,那么在加载完成后最好使用optimize table命令重新组织一下表

  C:更新聚集索引列的代价很高,因为会强制innodb将每个被更新的行移动到新的位置

  D:基于聚集索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临页分裂的问题,当行的主键值要求必须将这一行插入到某个已满的页中时,存储引擎会将该页分裂成两个页面来容纳该行,这就是一次页分裂操作,页分裂会导致表占用更多的磁盘空间

  E:聚集索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候

  F:二级索引可能比想象的更大,因为在二级索引的叶子节点包含了引用行的主键列。

  G:二级索引访问需要两次索引查找,而不是一次

  因为二级索引叶子节点中保存的不是指向行的物理位置的指针,而是行的主键值。这意味着通过二级索引查找行,存储引擎需要找到二级索引的叶子节点获得对应的主键值,然后根据这个主键值去聚集索引中查找对应的行,这里做了重复的工作,两次btree查找而不是一次,对于innodb,自适应哈希索引能减少这样的重复工作。

innodb和myisam物理存储的数据分布对比:

  myisam:

  是按照数据插入的顺序存储在磁盘上的,myisam中的主键索引和二级索引在结构上并没有什么不同,主键索引就是一个名为primary的唯一非空索引。

  innodb:

  因为innodb支持聚集索引,所以使用非常不同的方式存储同样的数据,innodb聚集索引包含了整个表的数据,而不是只有索引,因为在Innodb中,聚集索引就是表,所以不像myisam那样需要独立的行存储。聚集索引的每一个叶子节点都包含了主键值,事务ID,用于事务和MVCC的回滚指针以及所有剩余列的值,如果主键是一个列前缀索引,innodb也会包含完整的主键列和剩下的列的值。

  还有一点和myisam不同的是,innodb的二级索引和聚集索引很不同,innodb二级索引的叶子节点中存储的不是行指针,而是主键值,并以此作为指向行的指针,这样的策略减少了当出现行移动或者数据页的分裂时二级索引的维护工作,使用主键值当做指针会让二级索引占用更多的空间,换来的好处是,innodb在移动行时无须更新二级索引中的这个指针。

  在innodb表中按主键顺序插入行,如果正在使用Innodb表并且没有什么数据需要聚集,那么可以定义一个代理键作为主键,这种主键的数据应该和应用无关,最简单的方法是使用auto_increment自增列,这样可以保证数据行是按顺序插入的,对于根据主键做关联操作的性能也会更好。

  不要使用UUID来作为聚集索引,否则性能会很糟糕,因为它使得聚集索引的插入变得完全随机,使得数据没有任何聚集特性。因为UUID作为主键插入行不仅花费的时间更长,而且索引也更大,这一方面是因为主键字段变长了,另外一方面毫无疑问是由于页分裂导致时间变长和碎片导致的索引变大。因为主键的值是顺序的,所以Innodb把每一条记录都存储在上一条记录的后面,当达到页的最大填充因子时(innodb默认的最大填充因子是页大小的十六分之十五,留出部分空间用于以后修改),下一条记录就会写入新的页中,一旦数据按照这种顺序的方式加载,主键页就会近似被顺序的记录填满,这也正是所期望的结果(然而,二级索引页可能是不一样的)。

  在UUID主键下,因为新插入行的主键值不一定比前面的大,所以innodb无法简单地总是把新行插入到索引的最后,而是需要为新的行寻找合适的位置,通常是已有数据的中间位置,并且分配新的空间,这会增加很多额外的工作,并导致数据分布不够优化,下面是使用UUID作为主键的一些缺点:

A:写入的目标页可能已经刷到磁盘上并从缓存中移除,或者是还没有被加载到缓存中,innodb在插入前不得不先找到并从磁盘读取目标页到内存中,这将导致大量的随机IO

B:因为写入是乱序的,innodb不得不频繁地做页分裂操作,以便为新的行分配空间,页分裂会导致移动大量数据,一次插入最少需要修改三个页不是一个页

C:由于频繁的页分裂,页会变得稀疏并被不规则地填充,所以最终数据会有碎片

把这些随机值载入到聚集索引之后,也许需要做一次optimize table来重建表并优化页的填充。使用innodb时应该尽可能地按照主键顺序插入数据,并且尽可能地使用简单增加的聚簇键的值来插入新行。

注:顺序的主键什么时候会造成更坏的结果?

  对于高并发工作负载,在Innodb中按主键顺序插入可能会造成明显的争用,主键的上界会称为热点,因为所有的插入都发生在这里,所以并发插入可能导致间隙锁争用,另一个热点可能是auto_increment锁机制,如果遇到这个问题,则可能需要重新设计表或者应用,或者更改innodb_autoinc_lock_mode配置。