高性能MYSQL(学习笔记)—索引篇3

聚族索引

聚族索引不是一种单独的索引类型,而是一种数据存储方式。具体的细节依赖于其实现方式,但INNODB的聚族索引实际上在同一个结构中保存了B-Tree索引和数据行。聚族的意思是数据行和相邻的键值紧凑地存储在一起。

聚族索引的存储方式如下,叶子页包含了行的全部数据,但是节点页只包含了索引列,索引列在这里包含的是整数值。InnoDB将通过主键聚集数据,这也就是说图被索引的列就是主键列。如果没有主键列,InnoDB会选择一个唯一的非空索引代替。如果没有索引,InnoDB会隐式定义一个主键来作为聚族索引。InnoDB只聚集在同一个页面中的记录。包含相邻键值的页面可能会相差很远。

高性能MYSQL(学习笔记)—索引篇3_第1张图片

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

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

2、  数据访问更快。数据索引将索引和数据保存在同一个B-Tree中,因此从聚族索引中获取数据比非聚族中更快。

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

聚族索引的缺点:

1、  聚族数据最大限制地提高了I/O密集型应用的性能,但是如果数据全部放在内存中,则访问的顺序就没有那么重要了,聚族索引就没有什么优势了。

2、  插入速度严重依赖插入顺序。按照主键顺序插入是加载数据到InnoBD表中最快的方式。但是如果不按照主键顺序加载数据,那么在加载完后需要做一个OPTIMIZETABLE 命令重新组织一下表。

3、  更新聚族索引列的代价很高,因为会强制InnoDB将每一个被更新的行移动到新的位置。

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

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

6、  二级索引(非聚族索引)可能比想象要更大,因为在二级索引的叶子节点包含了引用行的主键列。

7、  二级索引需要两次索引查找。原因在于二级索引保存的行指针的实质。记住,二级索引叶子节点保存的不是指向行的物理位置,而是行的主键值。意味着二级索引查找行,存储引擎需要找到二级索引的叶子节点获得对应的主键值,然后根据这个值去聚族索引中找到相对应的行。这里做了重复工作:两次B-Tree查找而不是一次,对于InnoDB,自适应哈希索引能减少这样的工作。

InnoDBhe 和MyISAM的数据分布

数据在磁盘上的存储方式已经是最优的,但行的顺序是随机的。列的值是从1~100之间随机数,所以有很多重复值。

MySIAM 数据分布

非常简单,在旁的显示行号,从0开始递增,因为行是定长的,所以MyISAM可以从表的开头跳过所需的字节找到需要的行,这种分布很容易创建创建索引。

INNODB数据分布

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

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

在INNODB表中按照主键顺序插入行

为什么通常要设置主键?当你的表中没有什么数据需要聚集的时候,可以设置一个和应用无关的数据AUTO_INCREMENT自增列,这样可以保证数据行是按顺序写入,对于根据主键做关联操作的性能也会更好。最好避免随机的(不连续且值分布范围比较大)剧组索引。最好避免随机的剧组索引,特别是对于IO密集型的应用,例如,从性能考虑,使用UUID作为剧组索引会很糟糕,他使得聚族索引的插入变得完全随机,使得数据没有任何聚集特性。用主键值来做索引,每条记录都存储在上一条记录的后面,当达到页面的最大填充因子时,下一条记录就会写入新的页面中一旦数据按照这种方式加载,主键页就会近似于被记录填满。当使用UUID来做聚族索引时候,因为新行的主键值不一定比之前插入的大,所以INNODB无法简单的总是把新行插入到索引的最好,而是需要为新行寻找合适的位置需要重新分配空间。

缺点如下:

1、  写入的目标页无法确定是刷到磁盘并从缓存移除,或者是没有被加载到缓存中,INNODB需要找到并从磁盘读取目标页到内存中。将导致大量的随机IO。

2、  因为写入是乱序的,INNODB不得不频繁做页分裂操作,以便为新的行分配空间。这将导致大量的随机IO

3、  由于频繁的页分裂,页会变得稀疏并被不规则地填充,索引最终数据会有碎片。

  所以:使用INNODB,应该尽可能使用主键顺序插入数据,并且尽可能使用单调增加的聚族键的值来插入新行。

覆盖索引

通常都会根据查询的where条件来创建合适的索引,不过这只是索引的一方面,优秀的索引应该考虑整个查询,而不是单单where条件的一部分,MYSQL可以使用索引来直接获取列的数据,这样就不需要读取数据行。如果一个索引包所有需要查询的字段的值,我们称之为”覆盖索引”。覆盖索引是非常有用的工具,能够极大提高性能,好处如下:

1、  所以条目通常远小于数据行的大小,所以如果只需要读取索引,那么MYSQL就会极大减少数据访问量。

2、  索引是按照列的顺序存储的,所以对于IO密集型的范围查询会比随机从磁盘读取每一行的I/O要少得多。对于某些存储引擎,可以通过OPTIMIZE命令使得索引完全按照顺序排列。

3、  一些存储引擎入MYSIAM在内存中只缓存索引,数据则依赖于操作系统过来存储,因此要访问数据需要一次系统调用。这可能会导致严重的性能问题,尤其是那些系统调用占了数据访问中的最大开销场景。

4、  INNODB 的聚族索引,覆盖索引对INNODB表特别有用。INNODB二级索引在叶子节点中保存了行的主键值,所以如果二级主键能覆盖查询,则可以避免对主键索引的二级查询。

不是所有的覆盖索引都可以覆盖索引,覆盖索引必须要存储索引列的值,而哈希索引、空间索引和全文索引等都不存储索引列的值,所以MYSQL只能使用B-Tree索引做覆盖索引。

像EXPLAIN SELECT * FROM product WHERE actor=’SEAN CARREY’ andtitle like ‘%apple%’,这里索引无法覆盖该查询,原因有两个:

1、  没有任何索引能够覆盖这个查询,因为查询从表中选择了所有的列,而没有任何索引覆盖了所有的列。

2、 MYSQL不能在索引中执行LIKE 操作,只能做简单的操作(例如:等于、不等于、以及大于),MYSQL能在索引中做最左前缀匹配的LIKE比较,但是如果是通配符开头的like查询,存储引擎就无法做比较匹配,MYSQL服务器只能提取数据行的值而不是索引值来比较,有种方法可以重写查询并巧妙设计索引:

SELECT * FROM productsjoin(select prod_id from products where actor=’sean carry’ and title like ‘%apple%’)as t1 on(t1.prod_id = products.prod_id),这个叫做延迟关联,因为延迟了对列的访问,在查询第一阶段可以使用MYSQL覆盖索引,在from 子句子查询中匹配的prod_id,然后根据这些pro_id值在外层查询匹配获取需要的所有列的值。但是当where返回的行数很大的时候,很难达到优化的结果,大部分时间都花在读取和发送数据上了。

使用索引扫描来做排序

MYSQL有两种方式可以生成有序的结果:通过排序操作或者按照索引顺序扫描,如果explain 出来的type列值为“index”,则说明MYSQL使用了索引扫描出来做排序(不要和extra列的“using index”混淆了)。

扫描索引本身是很快的,因为只需要从一条记录移动到紧接着的下一条记录。但是如果索引不能覆盖查询所需要的全部列,那就不得不扫描每一条索引记录就返回表查询一次对应的行。只有当索引的列顺序和order by子句的顺序完全一致,并且所有列的排序方向都一样时,MYSQL才能使用索引来对结果做排序。如果查询需要关联多张表,则只有当order by 子句引用的字段全部为第一个表时,才能使用索引做排序。Order by 子句和查找型查询的限制是一样的:需要满足索引的最左前缀的要求;否则MYSQL都需要执行排序操作,而无法利用索引排序。

例如表 rantal中创建了这样的索引:retal_date,inventory_id,customer,

Key idx_fk_inventory_id(inventory_id),

Key idx_fx_customer_id(customer_id)

Key idx_fx_staff_id(staff_id)

以下是可以进行索引排序的:

… whereretal_date=’2018-04-22’ order by inventory_id desc

…whererental_date>’2018-04-22’ order by rental_date,inventory_id

因为以上一个是索引第一列被指为一个常数,而使用第二列进行排序,将两列组合起来,就形成了索引的最左前缀

下面是一些不能使用索引做排序的查询:

…rental_date=’2018-04-22’order by inventory_id desc,customer_id asc;不同方向排序,因为索引列是正序排序

rental_date=’2018-04-22’order by inventory_id ,staff_id;不在索引列中的列

rental_date=’2018-04-22’order by customer_id;无法组合成索引的最左前缀;

rental_date>’2018-04-22’order by inventory_id,customer_id;在索引的第一列上是范围查询条件,无法使用索引的列,但是如果机上 order by rental_date那就ok

rental_date=’2018-04-22’and  inventory_id in (1,2)order bycustomer_id;也是属于范围查询,无法索引

压缩前缀索引

  MYSIAM 压缩每一个索引块的方法是,先完全保存索引块中的第一个值,然后其他值和第一个值进行比较得到相同的前缀字节数和剩余不同的后缀部分,把这个部分存储起来即可。压缩块使用更少的空间,代价是某些操作可能更慢。

冗余和重复索引

1、  MYSQL唯一限制和主键限制都是通过索引实现的!所以无需对唯一限制和主键再建立索引!

2、  新增索引会导致INSERT UPDATE DELETE变慢。

索引和锁

索引可以让查询锁定更少的行。如果你的查询从不访问哪些不需要的行,那么就会锁定更少的行,从两个方面来看这个对性能都有好处,首先INNODB行锁效率很高,内存使用也很少,但是锁定行的时候仍然会带来额外的开销,其次锁定超过需要的行会增加锁争用并减少并发性。

INNODB只有在访问行的时候才会对其加锁,而索引能够减少INNODB访问的行数,从而减少锁的数量。但是这只有当INNODB在存储引擎层能够过滤掉所有不需要的行时候才有效,如果索引无法过滤掉不需要的行,那么在INNODB检索到数据并返回给服务层以后,MYSQL服务器才能应用where语句,这时候已经无法避免锁定行了。

Extra:Using where,表示MYSQL服务器将存储引擎返回行后再应用where过滤条件。

像:select actor_id from actor whereactor<>5 and actor_id<>1 for update;

这个MYSQL会锁住actor_id=1的行,也就是MYSQL使用了using where。

当你执行 select actor_id where actor whereactor_id=1 for update;时候,即使是使用了索引,INNODB也可能锁住一些不需要的数据,actor_id=1这条。如果不能使用索引查找的和锁定行的话,问题可能会更糟糕,MYSQL会做全表扫描并锁住所有行,而不管需不需要!


你可能感兴趣的:(高性能MYSQL学习笔记,覆盖索引,聚合索引)