MySQL系列(九)--MySQL高性能的索引策略:索引建立,索引优化

0一、索引究竟是怎么回事?mysql

    前面两篇说到了索引原理即数据结构,同时还讲到了其索引原则。那么查询一条SQL语句究竟是怎么执行的呢?或者说除了主键索引,其余的索引究竟是怎么一回事?今天的这篇主要任务就是理清这个关系,而后说说怎么优化建立的索引?这个很重要,绝对对你的工做有很大帮助,但愿能细心的体会,而且深刻理解,那么就开始吧。算法

    先来了解概念,以下:sql

    主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键也被称为聚簇索引(clustered index)。数据库

    非主键索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为二级索引(secondary index)。缓存

    根据上面的索引结构说明,咱们来讨论一个问题:基于主键索引和普通索引的查询有什么区别?服务器

    若是语句是 ,下面这条:数据结构

SELECT * from userinfo where id= 655;

即主键查询方式,则只须要搜索 ID 这棵 B+ 树;并发

    若是语句是,下面这条:函数

SELECT * from userinfo where name= 'aaron';

即普通索引查询方式,则须要先搜索name 索引树,获得 ID 的值为 655,再到 ID 索引树搜索一次。这个过程称为回表性能

    也就是说,基于非主键索引的查询须要多扫描一棵索引树。所以,咱们在应用中应该尽可能使用主键查询。


 

索引维护

    先从问题开始入手考虑,索引维护这个主题。

 

    为何公司规定主键ID通常是自增加,且是整型?

 

    性能方面考虑:

    B+ 树为了维护索引有序性,在插入新值的时候须要作必要的维护.若是插入新的行 ID 值为 700,则只须要在 R5 的记录后面插入一个新记录。若是新插入的 的 ID 值为 400,就相对麻烦了。须要逻辑上挪动后面的数据,空出位置。

    而更糟的状况是,若是 R5 所在的数据页已经满了,根据 B+ 树的算法,这时候须要申请一个新的数据页,而后挪动部分数据过去。这个过程称为页分裂。在这种状况下,性能天然会受影响。

    固然有分裂就有合并。当相邻两个页因为删除了数据,利用率很低以后,会将数据页作合并。合并的过程,能够认为是分裂过程的逆过程。

    自增主键的插入数据模式,正符合了咱们前面提到的递增插入的场景。每次插入一条新记录,都是追加操做,也不会触发叶子节点的分裂。而有业务逻辑的字段作主键,则每每不容易保证有序插入,这样写数据成本相对较高。

 

    存储方面考虑:

    除了考虑性能外,咱们还能够从存储空间的角度来看。假设你的表中确实有一个惟一字段,好比字符串类型的身份证号,那应该用身份证号作主键,仍是用自增字段作主键呢?

    因为每一个非主键索引的叶子节点上都是主键的值。若是用身份证号作主键,那么每一个二级索引的叶子节点占用约 20 个字节,而若是用整型作主键,若是是长整型(bigint)则是 8 个字节。

    显然,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。

    因此,从性能和存储空间方面考量,自增主键每每是更合理的选择。

 

    有没有什么场景适合用业务字段直接作主键的呢?

 

    好比,有些业务的场景需求是这样的:

        1.只有一个索引; 

        2.该索引必须是惟一索引。

    这就是典型的 KV 场景。直接将这个索引设置为主键,能够避免每次查询须要搜索两棵树。

 

0二、高性能的索引策略

    高效地选择和使用索引有不少种方式,其中有些是针对特殊案列的优化方式,有些则是针对特定行为的优化。

 

1.独立的列

    是指索引列不能是表达式的一部分,也不能是函数的参数。

    错误的方式,肉眼能够看出,可是mysql并不能识别出。

SELECT id FROM userinfo where id + 1 = 2;

    正确的方式是:

SELECT id FROM userinfo where id = 1;

2.前缀索引和索引选择性

    有时候须要索引很长的字符串,这会让索引变得大且慢。一个策略是哈希索引。一般能够索引开始的部分字符,这样能够大大节约索引空间,从而提升索引效果。但这样会下降索引的选择性。索引的选择性是指,不重复的索引值(也称为基数)和数据表的记录总数(#T)的比值,范围从1/#T到1之间。索引的选择性越高则查询效率越高,由于选择性高的索引让mysql在查找时过滤掉更多的行。

 

3.多列索引

    一个常见的错误就是,为每一个列建立独立的索引,或者按照错误的顺序建立多列索引。

creat table t(c1 int,c2 int,c3 int,key(c1),key(c2),key(c3));

    把"where 条件里的列都建上索引",这个是有点错误的想法,这个可能只是"一星"索引,其性能比起真正最优的索引可能差几个数量级。有时若是没法设计一个"三星"索引,那么不如忽略掉where子句,集中精力优化索引列的顺序,或者建立一个全覆盖索引。进而,引出"索引合并":

SELECT id FROM userinfo where age > 10 or sex ='M';

    通常会使用全表扫描的,除非改写成以下的两个表查询union的方式:

SELECT id FROM userinfo where age > 10union allSELECT id FROM userinfo where sex ='M';

    若是对 age和sex进行索引合并,仍是同样的查询:

SELECT id FROM userinfo where age > 10 or sex ='M';

可能将会大大改善其结果。

    索引合并策略有时候是一种优化的结果,但实际上更多时候说明了表上的索引建的很糟糕:

    1.当出现服务器对多个索引作相交操做时(一般有多个AND条件),一般意味着须要一个包含全部相关列的多列索引,而不是多个独立的单列索引。

    2.当服务器须要对多个索引作联合操做时(一般有多个or条件),一般须要耗费大量CPU和内存资源在算法的缓存、排序和合并操做上。特别是当其中有些索引的选择性不高,须要合并扫描返回的大量数据的时候。

 

4.选择合适的索引列顺序

    在一个多列B-Tree索引中,索引列的顺序意味着索引首先按照最左列进行排序,其次是第二列等等。因此,索引能够按照升序或者降序进行扫描。以知足精确符合顺序的order by、Group by 和 distinct 等子句的查询需求。

    当不须要考虑排序和分组时,将选择性最高的列放在前面一般是很好的。这时候索引的做用只是用于优化where条件的查找。在这种状况下,这样的设计的索引确实可以最快地过滤出须要的行,对于where子句中只使用了索引部分前缀的查询来讲选择性也更高。然而,性能不仅是依赖于全部索引列的选择性(总体基数),也和查询条件的具体值有关,也就是和值的分布有关。

 

    那么咱们应该怎么选择组合索引字段的顺序呢?

select * from userinfo where id = 2 and customer_id = 584;

    是应该建立一个(id,customer_id)索引仍是应该颠倒一下顺序?能够跑一些查询来肯定在这个表中值的分布状况,并却肯定那个列的选择性更高。

    先使用sum函数来看看where条件的分支对应的数据基数有多大:

select sum(id = 2 ), sum (customer_id = 584) from userinfo ;

    结果为: 

sum(id = 2 ): 7992 ; sum (customer_id = 584) : 30

    根据前面的法则,应该将索引列 customer_id  放到前面,由于对应条件customer_id 数量更小。最后,尽管关于选择性和基数的经验法则值得去研究和分析,但必定要记住别忘记了where子句的排序、分组和范围条件等其余因素。

 

5.聚族索引

    聚族索引并非一种单独的索引类型,而是一种数据存储方式。当表有聚族索引时,它的数据行实际上存放在索引的叶子页中。

InnoDB将经过主键聚族数据,若是没有定义主键,InnoDB会选择一个惟一的非空索引代替。若是没有这样的索引,InnoDB会隐式定义一个主键来做为聚族索引。InnoDB只汇集在同一个页面中的记录。包含相邻键值的页面可能会相距甚远。

    优势:

1.能够把相关的数据保存在一块儿。例如实现电子邮箱时,能够根据用户id来汇集数据,这样只须要从磁盘读取少数的数据页就能获取某个用户的所有邮件。若是没有使用聚族索引,则每封邮件均可能致使一次磁盘i/o。 

2.数据访问更快。 

3.使用覆盖索引扫描的查询能够直接使用页节点中的主键值。

    缺点:

1.聚族数据最大限度地提升了i/o密集型应用的性能,但若是数据所有都放在内存中,则访问的顺序就没那么重要了,聚族索引也就没什么优点了。 

2.插入速度严重依赖插入顺序。按照主键的顺序插入是加载数据到InnoDB表中最快的方式。 

3.更新聚族索引列的代价很高。 

4.聚族索引可能致使全表扫描行,尤为是行比较稀疏,或者因为页分裂致使数据不连续的时候。

    建议:最好尽量使用单调增长的聚族键的值来插入新行。这样能够减小写入的时候分页相关操做。

 

6.覆盖索引

 

    一般你们都会根据查询的where条件来建立合适的索引,不过这只是索引优化的一个方面。设计优秀的索引应该考虑到整个查询,而不是单单是where条件部分。若是一个索引包含(或者说覆盖)全部须要查询的字段的值,咱们就从称之为"覆盖索引".

    优势:

1.索引条目一般远小于数据行大小,因此若是只须要读取索引,那mysql就会极大地减小数据访问量。 

2.由于索引是按照列值顺序存储的,因此对于i/o密集型的范围查询会比随机从磁盘读取每一行数据的i/o要少得多。 

3.因为InnoDB的聚族索引,覆盖索引对InnoDB表特别有用。InnoDB的二级索引在叶子节点中保存了行的主键值,因此若是二级主键可以覆盖查询,则能够避免对主键索引的二次查询。

    发起一个被被覆盖的查询,在EXPLAIN 的 Extra列能够看到"Using index" 的信息。那就可使用这个索引作覆盖索引。

    注意:Extra列 的 "Usering where" 是不能够作索引的。有多是字段使用了like %%。

EXPLAIN SELECT ua.account_id,ua.user_id from user_account ua

能够根据account_id,user_id 作覆盖索引,也能够根据where条件全部值作覆盖索引。

 

7.使用索引扫描来作排序

    mysql有两种方式能够生成有序的结果:经过排序操做;或者按照索引顺序扫描。若是EXPLAIN 出来的type列的值为"index",则说明mysql使用了索引扫描来作排序。

    只要当索引的列顺序和order by子句的顺序彻底一致,而且全部列的排序方向都同样时,mysql才可以使用索引来对结果作排序。若是查询须要关联多张表,则只有当order by 子句引用的字段所有为第一个表时,才能使用索引作排序order by子句和查找型查询的限制是同样的。

    对于有这样一个表 rental 在列(rental_date,inventory_id,customer_id)建立这样一个索引。

where rental_date='2019-04-05' order by inventory_id,customer_id ;

即便order by 子句不知足索引的最左前缀的要求,也能够用于查询排序,这是由于索引的第一列被指定为一个常数。

where rental_date = '2019-04-05' order by inventory_id desc;

能够利用查询为索引的第一列提供了常量条件,而使用第二列进行排序,将两列组合在一块儿,就造成了索引的最左前缀。











#下面这个也是最左前缀索引:where rental_date = '2019-04-05' order by  rental_date , inventory_id ;#下面是一些不能使用索引作排序的查询:#使用了两种不一样的排序方向,可是索引列都是正序排序的:where rental_date = '2019-04-05' order by inventory_id desc , customer_id asc;#查询的order by 子句中引用了一个不在索引中的列:where rental_date = '2019-04-05' order by inventory_id , staff_id;#查询的where 和 order by 中的列没法组合成索引的最左前缀:where rental_date = '2019-04-05' order by customer_id;#查询在索引的第一列上是范围条件,因此mysql没法使用索引的其他列:where rental_date > '2019-04-05' order by inventory_id ,customer_id;#查询在inventory_id 列上有多个条件,对于排序来讲,这也是一种范围查询:where rental_date = '2019-04-05' and inventory_id in (1,2) order by customer_id ;

 

8. 冗余和重复索引

    mysql 容许在相同列上建立多个索引,mysql须要单独维护重复的索引,而且优化器在优化查询的时候也须要逐个地进行考虑,这会影响性能。重复索引是指在相同的列上按照相同的顺序建立的相同类型的索引。应该避免这样建立重复索引,发现之后也应该当即移除。

creat table test(ID int not null primary key,A int not null,UNIQUE(ID),index(ID))

    这个在建立的时候,就已经建立了重复的索引。

    大多数状况下都不须要冗余索引,应该尽可能扩展已有的索引而不是建立新索引。通常来讲表中的索引越多插入速度会越慢(这个在项目中已经实验过了,数据迁移项目),同时增长新索引将会致使insert / update / delete 等操做的速度变慢,特别是当新增索引后致使达到了内存瓶颈的时候。

 

9.索引和锁

    索引可让查询锁定更少的行。若是你的查询从不访问哪些不须要的行,那么就会锁定更少的行。首先,虽然InnoDB的行锁效率很高,内存使用页不多,可是锁定行的时候,仍然会带来额外开销;其次,锁定超过须要的行会增长锁争用并减小并发性。

    InnoDB只有在访问行的时候才会对其加锁。而索引可以减小InnoDB访问的行数,从而减小锁的数量。但只有当InnoDB在存储引擎层可以过滤掉全部不须要的行时才有效。


 

实战,案例一:

 

    1. 支持多种过滤条件

    好比有个表国家(country)列,这个列选择性一般不高,但可能会查询都会用到。sex的列选择性确定也很低,但也会在不少查询中用到。因此考虑到使用的频率,仍是建议在建立不一样组合索引的时候将(sex,country)列做为前缀。

    问题是,若是某个查询不限制性别,那么咱们应该怎么作?

    能够经过在查询条件中新增and sex in('m','f')来让mysql选择该索引。mysql可以匹配索引的最左前缀。但若是列有太多的值,就会让in()列表太长(这篇关于in语句与between对比文章),这样作久不太行了。

 

    设计的原则:

    考虑表上全部的选项。

    当设计索引时,不要只考虑须要哪些索引,还须要考虑对查询进行优化。若是发现某些查询须要建立新索引,可是这个索引会下降另外一些查询的效率,那么应该想一下是否能优化原来的查询。应该同时优化查询和索引以找到最佳的平衡。

    接下来,须要考虑其余常见的where 条件的组合,并须要了解哪些组合在没有合适索引状况下会很慢。其实这个索引(sex,country)还能够加上(sex,country,region,age).

 

    2.避免多个范围条件

    什么是范围查询?

    从explain的输出很难区分mysql是要查询范围值。可是咱们能够从值的范围和多个等于条件来得出不一样。

    假设有这样一个语句:

where eye_color in('blue')and hair_color in('black','red')and  sex in('m','f')and last_online > DATE_SUB(NOW(),INTERVAL 7 DAY)and age  between 18 and 20

    这个查询有一个问题,它有两个范围条件,last_online  and age , mysql last_online 列索引  and age 列索引,但没法同时使用它们。这个是没法解决的,建议多建立几个组合索引,可是也不要建立太多,索引建立太多,可能致使插入很慢。

 

    3.优化排序

    例如,若是where 子句只有sex 列,如何排序?

    对于那些选择性很是低的列,能够增长一些特殊的索引来作排序。 例如,能够建立(sex, rating) 索引用于下面的查询:

select * from profiles where sex = 'm' order by rating limit 10;

    若是须要翻页:

select * from profiles where sex = 'm' order by rating limit 100000, 10;

    不管如何建立索引,这种查询都是个严重的问题。由于随着偏移量的增长,mysql 须要花费大量的时间来扫描须要丢弃的数据。反范式化,预先计算和缓存多是解决这类查询的仅有策略,一个更好的办法是限制用户可以翻页查询的数量,实际上这对用户体验的影响不大,由于用户不多真正在意搜索结果的第10000页。

    优化这类索引的另外一个比较好的策略是使用延迟关联,经过使用覆盖索引查询返回须要的主键,在根据主键关联原表得到须要的行。这能够减小mysql扫描哪些须要丢弃的行数。

    如何高效的利用(sex, rating)索引进行排序和分页:

select * from profiles inner join ( select (primary key) from profiles where x.sex = 'm' order by rating limit 100000, 10 ) as x using (primary key) ;

 


 

实战演练二:如何给字符串创建索引?

    如今,几乎全部的系统都支持邮箱登陆,如何在邮箱这样的字段上创建合理的索引?

create table SUser(ID bigint unsigned primary key,email varchar(64))engine=innodb;

    通常的sql是这样的:

select f1, f2 from SUser where email='xxx';

    若是 email 这个字段上没有索引,那么这个语句就只能作全表扫描。建立索引:

alter table SUser add index index1(email);#或 alter table SUser add index index2(email(6));

    第一个语句建立的 index1 索引里面,包含了每一个记录的整个字符串;而第二个语句建立的index2 索引里面,对于每一个记录都是只取前 6 个字节。

    那么,这两种不一样的定义在数据结构和存储上有什么区别呢?

 

    最大的区别是:存储的数据变大。

    email(6) 这个索引结构中每一个邮箱字段都只取前 6 个字节,因此占用的空间会更小,这就是使用前缀索引的优点。但,这同时带来的损失是,可能会增长额外的记录扫描次数。

select id,name,email from SUser where email='fanrongaaron@xxx.com';

    若是使用的是 index1(即 email 整个字符串的索引结构),执行顺序是这样的:

 1.从 index1 索引树找到知足索引值是’fanrongaaron@xxx.com’的这条记录,取得 ID2 的值; 

 2.到主键上查到主键值是 ID2 的行,判断 email 的值是正确的,将这行记录加入结果集; 

 3.取 index1 索引树上刚刚查到的位置的下一条记录,发现已经不知足 email='fanrongaaron@xxx.com’的条件了,循环结束。

这个过程当中,只须要回主键索引取一次数据。

    若是使用的是 index2(即 email(6) 索引结构),执行顺序是这样的:

1.从 index2 索引树找到知足索引值是’fanron’的记录,找到的第一个是 ID1;   

2.到主键上查到主键值是 ID1 的行,判断出 email 的值不是’fanrongaaron@xxx.com’,这行记录丢弃; 

3.取 index2 上刚刚查到的位置的下一条记录,发现仍然是’fanron’,取出 ID2,再到 ID 索引上取整行而后判断,此次值对了,将这行记录加入结果集; 

 4.重复上一步,直到在 idxe2 上取到的值不是’fanron’时,循环结束。

在这个过程当中,要回主键索引取 4 次数据,也就是扫描了 4 行。对于这个查询语句来讲,若是你定义的 index2而是 email(7),也就是说取 email 字段的前 7 个字节来构建索引的话,即知足前缀’fanrong’的记录只有一个,也可以直接查到 ID2,只扫描一行就结束了。使用前缀索引,定义好长度,就能够作到既节省空间,又不用额外增长太多的查询成本。

    那么问题来了,当要给字符串建立前缀索引时,有什么方法可以肯定我应该使用多长的前缀呢?

    实际上,咱们在创建索引时关注的是区分度,区分度越高越好。由于区分度越高,意味着重复的键值越少。所以,咱们能够经过统计索引上有多少个不一样的值来判断要使用多长的前缀。

    首先,你可使用下面这个语句,算出这个列上有多少个不一样的值:


select count(distinct email) as L from SUser;#依次选取不一样长度的前缀来看这个值,好比咱们要看一下 4~7 个字节的前缀索引,能够用这个语句:selectcount(distinct left(email,4))as L4,count(distinct left(email,5))as L5,count(distinct left(email,6))as L6,count(distinct left(email,7))as L7,from SUser;

    前缀索引对覆盖索引的影响:

select id,email from SUser where email='fanrongaaron@xxx.com';#要求返回 id 和 email 字段。select id,name,email from SUser where email='fanrongaaron@xxx.com';

 

    若是使用 index1(即 email 整个字符串的索引结构)的话,能够利用覆盖索引,从index1 查到结果后直接就返回了,不须要回到 ID 索引再去查一次。而若是使用 index2(即email(6) 索引结构)的话,就不得不回到 ID 索引再去判断 email 字段的值。

    也就是说,使用前缀索引就用不上覆盖索引对查询性能的优化了,这也是你在选择是否使用前缀索引时须要考虑的一个因素。

 

    那么有没有更好的方式呢?其它方式:

 

    好比,咱们国家的身份证号,一共 18 位,其中前 6 位是地址码,因此同一个县的人的身份证号前 6 位通常会是相同的。按照咱们前面说的方法,可能你须要建立长度为 12 以上的前缀索引,才可以知足区分度要求。

    可是,索引选取的越长,占用的磁盘空间就越大,相同的数据页能放下的索引值就越少,搜索的效率也就会越低。

    若是咱们可以肯定业务需求里面只有按照身份证进行等值查询的需求还有没有别的处理方法呢?

    第一种方式是使用倒序存储。若是你存储身份证号的时候把它倒过来存,每次查询的时候,你能够这么写: 

select field_list from t where id_card = reverse('input_id_card_string');

    第二种方式是使用 hash 字段。你能够在表上再建立一个整数字段,来保存身份证的校验码,时在这个字段上建立索引。

alter table t add id_card_crc int unsigned, add index(id_card_crc);

    而后每次插入新记录的时候,都同时用 crc32() 这个函数获得校验码填到这个新字段。因为校验码可能存在冲突,也就是说两个不一样的身份证号经过 crc32() 函数获得的结果多是相同的,因此你的查询语句 where 部分要判断 id_card 的值是否精确相同。

select field_list from t where id_card_crc=crc32('input_id_card_string') and id_card='input_id_card_string'

这样,索引的长度变成了 4 个字节,比原来小了不少。使用倒序存储和使用 hash 字段这两种方法的异同点。

    首先,它们的相同点是,都不支持范围查询。倒序存储的字段上建立的索引是按照倒序字符串的方式排序的,已经没有办法利用索引方式。

    1.从占用的额外空间来看,倒序存储方式在主键索引上不会消耗额外的存储空间,而 hash 字段方法须要增长一个字段。固然,倒序存储方式使用 4 个字节的前缀长度应该是不够的,若是再长一点,这个消耗跟额外这个 hash 字段也差很少抵消了。

    2.在 CPU 消耗方面,倒序方式每次写和读的时候,都须要额外调用一次 reverse 函数,而 hash字段的方式须要额外调用一次 crc32() 函数。若是只从这两个函数的计算复杂度来看的话,reverse 函数额外消耗的 CPU 资源会更小些。

    3.从查询效率上看,使用 hash 字段方式的查询性能相对更稳定一些。由于 crc32 算出来的值虽然有冲突的几率,可是几率很是小,能够认为每次查询的平均扫描行数接近 1。而倒序存储方式毕竟仍是用的前缀索引的方式也就是说仍是会增长扫描行数。

总结:

1.直接建立完整索引,这样可能比较占用空间;

2.建立前缀索引,节省空间,但会增长查询扫描次数,而且不能使用覆盖索引。

3.倒序存储,再建立前缀索引,用于绕过字符串自己前缀的区度不够的问题;

4.建立 hash 字段索引,查询性能稳定,有额外的存储和计算消算消耗,跟第三种方式同样,都不支持范围扫描。

0三、选择索引依据是什么?

    前面都是直接给出索引规则,这个应该怎么操做,这个应该使用什么索引。可是到底怎么会选择这个索引呢,主键索引?

    假设你在维护一个市民系统,每一个人都有一个惟一的身份证号,并且业务代码已经保证了不会写入两个重复的身份证号。若是市民系统须要按照身份证号查姓名,就会执行相似这样的 SQL 语

select name from CUser where id_card = 'xxxxxxxyyyyyyzzzzz';

    因此,你必定会考虑在 id_card 字段上建索引。因为身份证号字段比较大,我不建议你把身份证号当作主键,那么如今你有两个选择,要么给id_card 字段建立惟一索引,要么建立一个普通索引。若是业务代码已经保证了不会写入重复的身份证号,那么这两个选择逻辑上都是正确的。

    1.从性能的角度考虑,你选择惟一索引仍是普通索引呢?选择的依据是什么呢?

    第一个节点之后说了主键索引和普通索引的区别。假设,执行查询的语句是:

SELECT * from userinfo where name='1000';

    这个查询语句在索引树上查找的过程,先是经过 B+ 树从树根开始,按层搜索到叶子节点,而后能够认为数据页内部经过二分法来定位记录。

    对于普通索引来讲,查找到知足条件的第一个记录 (1000,5000)后,须要查找下一个记录,直到碰到第一个不知足 id=5 条件的记录。对于惟一索引来讲,因为索引定义了惟一性,查找到第一个知足条件的记录后,就会中止继续检索。

 

    那么,这个不一样带来的性能差距会有多少呢?答案是,微乎其微。

 

    InnoDB 的数据是按数据页为单位来读写的。也就是说,当须要读一条记录的时候,并非将这个记录自己从磁盘读出来,而是以页为单位,将其总体读入内存。在 InnoDB 中,每一个数据页的大小默认是 16KB。

    由于引擎是按页读写的,因此说,当找到 name='1000' 的记录的时候,它所在的数据页就都在内存里了。那么,对于普通索引来讲,要多作的那一次“查找和判断下一条记录”的操做,就只须要一次指针寻找和一次计算。

 

    2.更新过程

    当须要更新一个数据页时,若是数据页在内存中就直接更新,而若是这个数据页尚未在内存中的话,在不影响数据一致性的前提下,InooDB 会将这些更新操做缓存在change buffer 中,这样就不须要从磁盘中读入这个数据页了。在下次查询须要访问这个数据页的时候,将数据页读入内存,而后执行 change buffer 中与这个页有关的操做。经过这种方式就能保证这个数据逻辑的正确性。

    change buffer,实际上它是能够持久化的数据。也就是说,change buffer 在内存中有拷贝,也会被写入到磁盘上。将 change buffer 中的操做应用到原数据页,获得最新结果的过程称为 merge。除了访问这个数据页会触发 merge 外,系统有后台线程会按期 merge。在数据库正常关闭(shutdown)的过程当中,也会执行 merge。

    显然,若是可以将更新操做先记录在 change buffer.,减小读磁盘,语句的执行速度会获得明显的提高。并且,数据读入内存是须要占用 buffer pool的,因此这种方式还可以避免占用内存,提升内存利用率。

    

    2.1 什么条件下可使用 change buffer 呢?

    对于惟一索引来讲,全部的更新操做都要先判断这个操做是否违反惟一性约束。好比,要插入(4,400) 这个记录,就要先判断如今表中是否已经存在 name=4 的记录,而这必需要将数据页读入内存才能判断。若是都已经读入到内存了,那直接更新内存会更快,就不必使用 change buffe了。

    所以,惟一索引的更新就不能使用 change buffer,实际上也只有普通索引可使用。再一块儿来看看若是要在这张表中插入一个新记录 (4,400) 的话,InnoDB 的处理流程是怎样的。

    第一种状况是,这个记录要更新的目标页在内存中。这时,InnoDB 的处理流程以下:

 1.对于惟一索引来讲,找到 3 和 5 之间的位置,判断到没有冲突,插入这个值,语句执行结束;

 2.对于普通索引来讲,找到 3 和 5 之间的位置,插入这个值,语句执行结束。

    第二种状况是,这个记录要更新的目标页不在内存中。这时,InnoDB 的处理流程以下:

对于惟一索引来讲,须要将数据页读入内存,判断到没有冲突,插入这个值,语句执行结束;

对于普通索引来讲,则是将更新记录在 change buffe,语句执行就结束了。

change buffer 由于减小了随机磁盘访问,因此对更新性能的提高是会很明显的。

 

    2.2 change buffer 的使用场景

    change buffer 只限于用在普通索引的场景下,而不适用于惟一索引。

    注意:所以,对于写多读少的业务来讲,页面在写完之后立刻被访问到的几率比较小,此时 change buffer 的使用效果最好。这种业务模型常见的就是帐单类、日志类的系统。反过来,假设一个业务的更新模式是写入以后立刻会作查询,将更新先记录在 change buffer,但以后因为立刻要访问这个数据页,会当即触发 merge 过程。这样change buffer 反而起到了反作用。

    redo log 主要节省的是随机写磁盘的 IO 消耗(转成顺序写),而 change buffer 主要节省的则是随机读磁盘的 IO 消耗。

 

0四、总结

    在选择索引和编写利用这些索引的查询时,有以下三个原则始终须要记住:

    1.单访问时很慢的。特别是在机械键盘存储中。若是服务器从存储中读取一个数据块只是为了获取其中一行,那么久浪费了不少工做。最好读取的块中能包含尽量多所须要的行。使用索引能够建立位置引用以提高效率。

    2.按顺序访问范围数据是很快的,这有两个缘由。第一,顺序I/O不须要多行磁盘寻道,因此比随机I/O要快不少。第二是,若是服务器可以按须要顺序读取数据,那么就再也不须要额外的排序操做,而且Group By 查询也无须再作排序和将行按组进行聚合计算了。

    3.索引覆盖查询是快的。若是一个索引包含了查询须要的全部列,那么存储引擎就不须要再回表查找行。这避免了大量的单行访问。

相关文章
相关标签/搜索