一、前提概念
随机IO与顺序IO:连续(TS)和随机(TR),是指本次IO给出的初始扇区地址,和上一次IO的结束扇区地址,是不是完全连续的,或者相隔不多的,如果是,则本次IO应该算是一个连续IO,如果相差太大,则算一次随机IO。连续IO,因为本次初始扇区和上次结束扇区相隔很近,则磁头几乎不用换道或换道时间极短;如果相差太大,则磁头需要很长的换道时间,如果随机IO很多,导致磁头不停换道,效率大大降底。 根据《数据库索引优化与设计》,TR=10ms,TS=0.01ms。
回表:每当索引检索到1个满足条件的就再到表里面去通过主键查找符合查询条件的,每一次回表都产生一次随机IO。
二、B+Tree 索引
B+Tree 索引是大多数 MySQL 存储引擎的默认索引类型。
因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。除了用于查找,还可以用于排序和分组。
可以指定多个列作为索引列,多个索引列共同组成键。
B+Tree 索引适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。
如果不是按照索引列的顺序进行查找,则无法使用索引。
InnoDB 的 B+Tree 索引分为主索引和辅助索引。
主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
辅助索引的叶子节点的 data 域记录着主键的值,因此在使用辅助索引进行查找时,需要先查找到主键值,然后再到主索引中进行查找。
三、举例分析
Cust(id intprimary key, cnoint, age int, fnamechar(1), lnamechar(1), city char(1) key idx_ln_fn_age_city(lname,fname,age,city));
现有查询语句:Select cno from cust where lname= ‘j’ and fname= ‘d’ and city = ‘L’ order by age
假设满足条件的行数是 n,总行数是 m,TR = 10 ms,TS = 0.01 ms;
首先在联合索引上,耗时: TR * 1 + TS * n;
由于 cno 不在联合索引上,所以查询时需要回表;
使用索引的情况下,RT = TR * 1 + TS * n + TR * n = 10 + 10.01 * n;
全表扫描的情况下,RT = TR * 1 + TS * m + T(sort) = 10 + 0.01 * m;
如果 m / n < 1000,索引优于全表扫描;
如果 m / n > 1000,索引劣于全表扫描。
扩展:如果查询的是 id、lname、fname、age 或 city,则不需要回表,RT = TR * 1 + TS * n = 10 + 0.01 * n;
四、mysql 为什么将 B+Tree 作为默认索引
二叉树是搜索效率最高的,但是实际上大多数的数据库存储却并不使用二叉树。其原因是,索引不止存在内存中,还要写到磁盘上。一棵100万节点的平衡二叉树,树高20。一次查询可能需要访问20个数据块。在机械硬盘时代,从磁盘随机读一个数据块需要10 ms左右的寻址时间。也就是说,对于一个100万行的表,如果使用二叉树来存储,单独访问一个行可能需要20个10 ms的时间。
为了让一个查询尽量少地读磁盘,就必须让查询过程访问尽量少的数据块。那么,我们就不应该使用二叉树,而是要使用“N叉”树。这里,“N叉”树中的“N”取决于数据块的大小。
以InnoDB的一个整数字段索引为例,这个N差不多是1200。这棵树高是4的时候,就可以存1200的3次方个值,这已经17亿了。考虑到树根的数据块总是在内存中的,一个10亿行的表上一个整数字段的索引,查找一个值最多只需要访问3次磁盘。其实,树的第二层也有很大概率在内存中,那么访问磁盘的平均次数就更少了。