InnoDB的索引方案
目录项和用户记录的区别
聚簇索引
二级索引
联合索引
InnoDB的B+数索引的注意事项
B+树索引的根节点在诞生之后不会再移动
内节点中目录项记录的唯一性
一个页至少两条记录
索引的一些结论
索引的代价
覆盖索引
如何挑选索引?
只为用于搜索、排序或分组的列创建索引
考虑列的基数
索引列的类型尽量小
索引字符串值得前缀
索引列在比较表达式中单独出现
主键插入顺序
“B+树索引在空间和时间上都有代价,不要乱建索引!
B+树索引适用于下边这些情况:
全值匹配 匹配左边的列 匹配范围值 精确匹配某一列并范围匹配另外一项 用于排序 用于分组 B+树索引不能用下边这些情况:
”
ASC、DESC混用:不能高效地利用索引 WHERE子句中出现非排序使用到的索引列 排序列包含非同一个索引的列 排序列使用了复杂的表达式
知识回顾
在行记录中有两个属性对索引至关重要——record_type
(代表记录类型,0代表普通记录,1代表索引记录,2代表最小记录,3代表最大记录)和next_record
(到下一条记录的地址偏移)。在页中还有一个叫做页面目录的元素,我们可以根据主键的大小,使用二分快速的定位到目标记录所在的槽即可找到指定的记录。
为了节省篇幅,我们可以这样来表示页里面的记录:
那么如果我们要在很多页,甚至不使用主键查找的时候呢?如果没有一个合理的方案我们只能遍历所有的记录,这无疑是效率非常低下的。
InnDB索引
为了让页之间的数据有规律,能让我们使用特定的算法(如二分)定位到指定页。我们规定:
下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值。如果一个页中的记录超过一定值,我们就新增一个页,然后将原来页中的一部分记录移动到新增页。因为页与页之间是双链表的结构,因此新增和删除都很简单。
给所有页都建立一个目录项。
InnoDB的索引方案
目录项和记录页是差不多的,只不过目录项的两个列是主键和页号。InnoDB的设计者使用record_type
为1的页代表目录项记录。这些目录项可以放在一个专门记录目录项的页当中:
一般还会有一个更高级的目录存储着目录项记录的页:
目录项和用户记录的区别
目录项的 record_type
是1,而普通用户记录的record_type
是0。目录项记录只有主键值和页的编号两个列,而普通的用户记录可能包含自己定义很多列,另外还有隐藏列 只要在存储目录项的页中的主键值最小的目录项记录 min_rec_mask
的值为1,其他的都是0.
聚簇索引
上面的B+数本身就是一个目录,或者说本身就是一个索引,它有两个特点:
使用记录主键值的大小进行记录和页的排序,这包括三个方面的含义
页内的记录是按照主键的大小顺序排成一个单向链表 各个存放用户记录的页也是根据页中用户记录的主键大小顺序排成一个双向链表 存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个双向链表
我们将具有这两个特点的B+树称为聚簇索引,所有完整的用户记录都放在这个聚簇索引的叶子节点。聚簇索引不需要我们在Mysql语句中显式的使用INDEX语句去创建,InnoDB会自动为我们创建聚簇索引,聚簇索引也是数据的存储方式。
二级索引
加入我们以别的列作为搜索条件时,可以多建几颗B+树,不同的B+树的数据采用不同的排序规则。这种B+树与上面的聚簇索引有些许不同:
使用记录指定列的大小进行记录和页的排序; B+树的叶子节点存储的并不是完整的用户记录,而只是指定列+主键两个列的值 目录项记录中不再是主键+页号的搭配,而是指定列+页号的搭配
也就是如果我们使用别的列作为搜索条件,还需要先找到对应主键,然后再从聚簇索引中查找到完整的用户记录。我们也将这个过程称为回表
联合索引
我们可以同时以多个列作为排序规则,也就是同时为多个列建立索引,比方说我们想让B+数按照C2和C3列的大小进行排序,这样包含了两条排序规则:
先把各个记录和页按照C2进行排序 在记录的C2列相同的情况下,采用C3列进行排序
InnoDB的B+数索引的注意事项
B+树索引的根节点在诞生之后不会再移动
每当一个表创建了一个B+树索引之后,都会为这个索引创建一个根节点页面。最开始表中没有数据的时候,每个B+树索引对应的根节点中既没有用户记录,也没有目录项记录。
向表中插入用户记录的时候,先把用户记录存储到这个根节点中,当根节点中的可用空间用完时继续插入记录,此时会将根节点中所有记录复制到一个新分配的页,然后对这个新页进行分裂操作,得到另外一个新页,然后根节点升级成存储目录项记录的页
内节点中目录项记录的唯一性
在B+树中的同一层内节点的目录项记录除页号这个字段以外是唯一的。因为新插入记录必须能找到自己在哪个页。
一个页至少两条记录
保证效率
B+树索引的使用
索引的一些结论
每个索引都对应一颗B+树。最下边是叶子节点,其余都是内节点。叶子节点存储所有的用户记录,所有目录项存储在内节点中。 InnoDB会自动为主键建立聚簇索引 用户可以为自己感兴趣的列建立二级索引。二级索引由索引列+主键组成,通过回表操作找到对应的用户记录 B+树中每层的节点都是按照从小到大的顺序排列而组成了双向链表 通过索引查找记录是从B+树的根节点开始,一层一层向下所有,由于每个页面都按照索引列的值建立了PageDirectort所以查找起来非常快。
索引的代价
空间代价:每个索引都对应一颗B+树。
时间代价:插入一条记录要对所有的B+树进行维护,降低性能。
覆盖索引
为了告别回表操作带来的性能损耗,最好在查询列表里只包含索引列。因此不建议使用通配符*
作为查询列表,最好把要查询的列一次注明。
如何挑选索引?
只为用于搜索、排序或分组的列创建索引
也就是说只为出现在WHERE子句的列、连接子句的连接列、或者出现再ORDER BY或GROUP BY的子句中的列创建索引
考虑列的基数
基数是指某一列中不重复数据的个数。最好为基数大的列建立索引,例如当基数为1的时候,为该列创建索引是没用的,无法合理的利用索引。
索引列的类型尽量小
索引占用空间小 索引比较快
索引字符串值得前缀
假设我们字符串很长,那么存储一个字符串就需要占用很大得存储空间,在我们需要为这个字符串列创建索引时,会出现索引占用空间大和字符串比较占用时间长两个问题。所以可以考虑使用字符串得前几个字符进行索引。
Key idx_name_birthday_phone_number (name(10)), birthday, phone_number
索引列在比较表达式中单独出现
WHERE my_col * 2 < 4
WHERE my_col < 4 / 2
只有第二个表达式能用到索引
“如果索引列在比较表达式中不是以单独列的形式出现,而是以某个表达式,或者函数调用形式出现的话,是用不到索引的。
”
主键插入顺序
如果主键随机插入,根据我们上文所讲,聚簇索引的列是根据主键来排序的,这样会使数据频繁地复制到新增页,使得性能降低,所以推荐使用主键自增。
MyISAM中的索引方案的简单介绍
InnoDB中索引即数据,也就是聚簇索引的那颗B+树的叶子节点中已经把所有完整的用户记录都包含了,但MyISAM却将索引和数据分开存储:
将表中的记录按照记录的插入顺序单独存储在一个文件中,称为数据文件,可以通过行号而快速访问到一条记录 使用MyISAM存储引擎的表会把索引信息另外存储到一个称为索引文件的文件中,不过这里是主键+行号的组合,MyISAM中建立的索引相当于全部都是二级索引! 如果记录占用空间是固定的,就可以轻松算出记录在数据文件中的地址偏移量,如果采用的是变长记录格式MYISAM将在索引叶子节点存储数据文件中的地址偏移量