数据库索引

看博客、看书、总结一下。

目录:

1.什么是索引

2.索引分类

3.索引的数据结构-----B+树索引

4.索引的数据结构-----哈希索引

5.innodb引擎的索引(聚集)

6.mylsm引擎的索引(非聚集)

7.聚集索引和非聚集索引的选择

8.索引的优缺点

9.索引的使用场景与优化

10.mysql的索引操作及优化分析


<!----分割线---->


1.什么是索引

索引就像是书的目录,是与表或视图关联的磁盘上结构,可以加快从表或视图中检索行的速度。索引中包含由表或视图中的一列或多列生成的键。这些键存储在一个结构(例如BTree)中,使SQL可以快速有效地查找与键值关联的行。防止对图标进行全表扫描。

索引提供指向存储在表的指定列中的数据值的指针,然后根据您指定的排序顺序对这些指针排序。数据库使用索引以找到特定值,然后顺指针找到包含该值的行。这样可以使对应于表的SQL语句执行得更快,可快速访问数据库表中的特定信息。

当表中有大量记录时,若要对表进行查询,第一种搜索信息方式是全表搜索,是将所有记录一一取出,和查询条件进行一一对比,然后返回满足条件的记录,这样做会消耗大量数据库系统时间,并造成大量磁盘I/O操作;第二种就是在表中建立索引,然后在索引中找到符合查询条件的索引值,最后通过保存在索引中的ROWID(相当于页码)快速找到表中对应的记录。


2.索引分类

普通索引
最基本的索引类型,没有唯一性之类的限制。一般索引不产生数据约束作用,其功能主要是对字段建立索引表,以提高数据查询速度


唯一索引
唯一索引是不允许其中任何两行具有相同索引值的索引。
当现有数据中存在重复的键值时,大多数数据库不允许将新创建的唯一索引与表一起保存。数据库还可能防止添加将在表中创建重复键值的新数据。例如,如果在 employee 表中职员的姓 (lname) 上创建了唯一索引,则任何两个员工都不能同姓。唯一索引有两个作用,一个是数据约束,一个是数据索引,其中数据约束主要用来保证数据的完整性,唯一索引产生的索引记录中每一条记录都对应一个唯一的ROWID。


主键索引
简称为主索引,数据库表中一列或列组合(字段)的值唯一标识表中的每一行。该列称为表的主键。
在数据库关系图中为表定义主键将自动创建主键索引,主键索引是唯一索引的特定类型。该索引要求主键中的每个值都唯一。当在查询中使用主键索引时,它还允许对数据的快速访问。主关键字索引产生的索引同唯一索引,只不过它是在数据库建立主关键字时系统自动建立的


候选索引
与主索引一样要求字段值的唯一性,并决定了处理记录的顺序。在数据库和自由表中,可以为每个表建立多个候选索引。


聚集索引
也称为聚簇索引,在聚集索引中,表中行的物理顺序与键值的逻辑(索引)顺序相同。一个表只能包含一个聚集索引
索引不是聚集索引,则表中行的物理顺序与键值的逻辑顺序不匹配。与非聚集索引相比,聚集索引通常提供更快的数据访问速度。聚集索引更适用于对很少对基表进行增删改操作的情况。


非聚集索引
也叫非簇索引,在非聚集索引中,数据库表中记录的物理顺序与索引顺序可以不相同。一个表中只能有一个聚集索引,但表中的每一列都可以有自己的非聚集索引


3.索引的数据结构-----B+树索引



对于数据存储来说树是比较好的载体加快磁盘的数据定位,常见的树有下面几种

搜索二叉树
每个节点有两个子节点,数据量的增大必然导致高度的快速增加,显然这个不适合作为大量数据存储的基础结构。

B树
一棵m阶B树是一棵平衡的m路搜索树。B树上大部分的操作所需要的磁盘存取次数和B树的高度是成正比的,在B树中可以检查多个子结点,由于在一棵树中检查任意一个结点都需要一次磁盘访问,所以B树避免了大量的磁盘访问。一个 m 阶的B树满足以下条件:
每个结点至多拥有m棵子树;
根结点至少拥有两颗子树(存在子树的情况下);
除了根结点以外,其余每个分支结点至少拥有 m/2 棵子树;
所有的叶结点都在同一层上;
有 k 棵子树的分支结点则存在 k-1 个关键码,关键码按照递增次序进行排列;
关键字数量需要满足ceil(m/2)-1 <= n <= m-1;
数据库索引

B树的操作

B+树
一棵m阶B树是一棵平衡的m路搜索树。以一个m阶树为例满足以下条件:
根结点只有一个,分支数量范围为[2,m];
分支结点,每个结点包含分支数范围为[ceil(m/2), m];
分支结点的关键字数量等于其子分支的数量减一,关键字的数量范围为[ceil(m/2)-1, m-1],关键字顺序递增;
所有叶子结点都在同一层;

B+树性质
n棵子tree的节点包含n个关键字,不用来保存数据而是保存数据的索引。
所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
所有的非终端结点可以看成是索引部分,结点中仅含其子树中的最大(或最小)关键字。

数据库索引


B+树的插入


B+树的删除



4.索引的数据结构-----哈希索引

哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快。索引列会被存储在匹配到的hash bucket里面的表里,这个表里会有实际的数据行指针,再根据实际的数据行指针查找对应的数据行


哈希索引图解

数据库索引

搜索过程
哈希索引比起B树索引简单,因为它不需要遍历B树,所以访问速度会更快。要查找一行数据或者处理一个where子句,SQL Server引擎需要做下面几件事
根据where条件里面的参数生成合适的哈希函数
索引列进行匹配,匹配到对应hash bucket,找到对应hash bucket意味着也找到了对应的数据行指针(row pointer)
读取数据

哈希索引缺点
因为Hash索引比较的是经过Hash计算的值,所以只能进行等式比较,不能用于范围查询
由于哈希值是按照顺序排列的,但是哈希值映射的真正数据在哈希表中就不一定按照顺序排列,所以无法利用Hash索引来加速任何排序操作
不能用部分索引键来搜索,因为组合索引在计算哈希值的时候是一起计算的。
当哈希值大量重复且数据量非常大时,其检索效率并没有Btree索引高的。

5.innodb引擎的索引


InnoDB使用的是聚簇索引,将主键组织到一棵B+树中,而行数据就储存在叶子节点上,若使用"where id = 14"这样的条件查找主键,则按照B+树的检索算法即可查找到对应的叶节点,之后获得行数据。若对Name列进行条件搜索,则需要两个步骤:第一步在辅助索引B+树中检索Name,到达其叶子节点获取对应的主键。第二步使用主键在主索引B+树种再执行一次B+树检索操作,最终到达叶子节点即可获取整行数据。

搜索图解

数据库索引

数据库索引



innodb数据页结构

页是InnoDB存储引擎管理数据库的最小磁盘单位。页类型为B-Tree node的页,存放的即是表中行的实际数据了。
InnoDB中的页大小为16KB,且不可以更改
InnoDB可以将一条记录中的某些数据存储在真正的数据页面之外,即作为行溢出数据。MySQL的varchar数据类型可以存放65535个字节,但实际只能存储65532个。同时InnoDB是B+树结构的,因此每个页中至少应该有两个行记录,否则失去了B+树的意义,变成了链表,所以一行记录最大长度的阈值是8098,如果大于这个值就会将其存到溢出行中。

数据页组成部分
File Header(文件头)
Page Header(页头)
Infimun + Supremum Records
User Records(用户记录,即行记录)
Free Space(空闲空间)
Page Directory(页目录)
File Trailer(文件结尾信息)

Page Directory
页目录中存放了记录的相对位置,有些时候这些记录指针称为Slots(槽)或者目录槽,与其他数据库不同的是,InnoDB并不是每个记录拥有一个槽,InnoDB中的槽是一个稀疏目录,即一个槽中可能属于多个记录,最少属于4个目录,最多属于8个目录。槽中记录按照键顺序存放,这样可以利用二叉查找迅速找到记录的指针。但是由于InnoDB中的Slots是稀疏目录,二叉查找的结果只是一个粗略的结果,所以InnoDB必须通过recorder
header中的next_record来继续查找相关记录。同时slots很好的解释了recorder header中的n_owned值的含义,即还有多少记录需要查找,因为这些记录并不包括在slots中。


1.在File header中,FIL+PAGE_PREV,FIL_PAGE_NEXT两个表示当前页的上一页和下一页,由此可以看出叶子节点是双向链表串起来的。如下图
数据库索引
2.Infimum和Supremum记录
在InnoDB存储引擎中,每个数据页中有两个虚拟的行记录,用来限定记录的边界。Infimum记录是比该页中任何主键值都要小的值,Supremum指比任何可能大的值还要大的值。这两个值在页创建时被建立,并且在任何情况下不会被删除。
数据库索引
由上图可以看出,行记录是记录在页中的,同时是在页内行记录之间也是双向链表链接的(在网上有看到说是单链表的)

B+树配合数据页查询数据流程
首先通过B+树索引找到叶节点,再找到对应的数据页,然后将数据页加载到内存中,通过二分查找Page Directory中的槽,查找出一个粗略的目录,然后根据槽的指针指向链表中的行记录,之后在链表中依次查找。
需要注意的地方是,B+树索引不能找到具体的一条记录,而是只能找到对应的页。把页从磁盘装入到内存中,再通过Page
Directory进行二分查找,同时此二分查找也可能找不到具体的行记录(有可能会找到),只是能找到一个接近的链表中的点,再从此点开始遍历链表进行查找。



6.mylsm引擎的索引

MyISM使用的是非聚簇索引,非聚簇索引的两棵B+树看上去没什么不同,节点的结构完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有任何差别。由于索引树是独立的,通过辅助键检索无需访问主键的索引树。

搜索图解

数据库索引

数据库索引




7.聚集索引和非聚集索引的选择

两种索引的选择

动作描述

使用聚集索引

使用非聚集索引

列经常被分组排序

返回某范围内的数据

不应

一个或极少不同值

不应

不应

小数目的不同值

不应

大数目的不同值

不应

频繁更新的列

不应

外键列

主键列

频繁修改索引列

不应



事实上,我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。如:返回某范围内的数据一项。比如您的某个表有一个时间列,恰好您把聚合索引建立在了该列,这时您查询2004年1月1日至2004年10月1日之间的全部数据时,这个速度就将是很快的,因为您的这本字典正文是按日期进行排序的,聚类索引只需要找到要检索的所有数据中的开头和结尾数据即可;而不像非聚集索引,必须先查到目录中查到每一项数据对应的页码,然后再根据页码查到具体内容。

8.索引的优缺点


优点
通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性
可以大大加快 数据的检索速度,这也是创建索引的最主要的原因。
可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
在使用分组和排序 子句进行数据检索时,同样可以显著减少查询中分组和排序的时间
通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

缺点
创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度

9.索引的使用场景与优化


适用索引
在经常需要搜索的列上,可以加快搜索的速度;
在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;
在经常用在连接的列上,这 些列主要是一些外键,可以加快连接的速度;
在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的;
在经常需要排序的列上创 建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;
在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度

不适用索引
对于那些在查询中很少使用或者参考的列不应该创建索引。这是因 为,既然这些列很少使用到,因此有索引或者无索引,并不能提高查询速度。相反,由于增加了索引,反而降低了系统的维护速度和增大了空间需求。
对于那 些只有很少数据值的列也不应该增加索引。这是因为,由于这些列的取值很少,例如人事表的性别列,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。
对于那些定义为text, image和bit数据类型的列不应该增加索引。这是因为,这些列的数据量要么相当大,要么取值很少。
修改性能远远大于检索性能时,不应该创建索 引。这是因为,修改性能和检索性能是互相矛盾的。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。因 此,当修改性能远远大于检索性能时,不应该创建索引。

10.mysql的索引操作及优化分析


创建索引

//主键索引
ALTER TABLE 'table_name' ADD PRIMARY KEY 'index_name' ('column');

//唯一索引
ALTER TABLE 'table_name' ADD UNIQUE 'index_name' ('column');

//普通索引
ALTER TABLE 'table_name' ADD INDEX 'index_name' ('column');

//全文索引
ALTER TABLE 'table_name' ADD FULLTEXT 'index_name' ('column');

//组合索引
ALTER TABLE 'table_name' ADD INDEX 'index_name' ('column1', 'column2', ...);
//例如:创建一个组合索引
ALTER TABLE user_test ADD INDEX idx_user(user_name , city , age);


<!----分割线---->


索引有效的查询---全值匹配

指的是和索引中的所有列进行匹配,如:以上面创建的索引为例,在where条件后可同时查询(user_name,city,age)为条件的数据。(注:与where后查询条件的顺序无关,这里是很多同学容易误解的一个地方)


索引有效的查询---匹配最左前缀
指优先匹配最左索引列,如:上面创建的索引可用于查询条件为:(user_name )、(user_name, city)、(user_name , city , age)
注:满足最左前缀查询条件的顺序与索引列的顺序无关,如:(city, user_name)、(age, city, user_name)


索引有效的查询---匹配列前缀
指匹配列值的开头部分,如:查询用户名(user_name就是列值)以feinik开头的所有用户
SELECT * FROM user_test WHERE user_name LIKE 'feinik%';


索引有效的查询---匹配范围值
如:查询用户名以feinik开头的所有用户,这里使用了索引的第一列
SELECT * FROM user_test WHERE user_name LIKE 'feinik%';


<!----分割线---->


索引无效的查询---where查询条件中不包含索引列中的最左索引列
SELECT * FROM user_test WHERE city = '广州';

SELECT * FROM user_test WHERE age= 26;

SELECT * FROM user_test WHERE city = '广州' AND age = '26';


索引无效的查询---where查询条件中即使含索引列中的最左索引列,但是匹配列值的结尾
SELECT * FROM user_test WHERE user_name like '%feinik';

索引无效的查询---如果where查询条件中有某个列的范围查询,则其右边的所有列都无法使用索引优化查询
SELECT * FROM user_test WHERE user_name = 'feinik' AND city LIKE '广州%' AND age = 26;

<!----分割线---->

选择合适的组合索引列顺序

在组合索引的创建中索引列的顺序非常重要,正确的索引顺序依赖于使用该索引的查询方式,对于组合索引的索引顺序可以通过经验法则来帮助我们完成:将选择性最高的列放到索引最前列,该法则与前缀索引的选择性方法一致,但并不是说所有的组合索引的顺序都使用该法则就能确定,还需要根据具体的查询场景来确定具体的索引顺序。


<!----分割线---->


覆盖索引
如果一个索引(如:组合索引)中包含所有要查询的字段的值,那么就称之为覆盖索引,如:
SELECT user_name, city, age FROM user_test WHERE user_name = 'feinik' AND age > 25;
因为要查询的字段(user_name, city, age)都包含在组合索引的索引列中,所以就使用了覆盖索引查询,查看是否使用了覆盖索引可以通过执行计划中的Extra中的值为Using index则证明使用了覆盖索引,覆盖索引可以极大的提高访问性能。

前缀索引
有时候需要索引很长的字符列,这会增加索引的存储空间以及降低索引的效率,一种策略是可以使用哈希索引,还有一种就是可以使用前缀索引,前缀索引是选择字符列的前n个字符作为索引,这样可以大大节约索引空间,从而提高索引效率。
前缀索引是一种能使索引更小,更快的有效办法,但是MySql无法使用前缀索引做ORDER BY 和 GROUP BY以及使用前缀索引做覆盖扫描。

计算n值
SELECT COUNT(DISTINCT index_column)/COUNT(*) FROM table_name; -- index_column代表要添加前缀索引的列
注:通过以上方式来计算出前缀索引的选择性比值,比值越高说明索引的效率也就越高效。
然后
SELECT
COUNT(DISTINCT LEFT(index_column,1))/COUNT(*),
COUNT(DISTINCT LEFT(index_column,2))/COUNT(*),
COUNT(DISTINCT LEFT(index_column,3))/COUNT(*)
...
FROM table_name;
注:通过以上语句逐步找到最接近于(1)中的前缀索引的选择性比值,那么就可以使用对应的字符截取长度来做前缀索引了

建立前缀索引
ALTER TABLE table_name ADD INDEX index_name (index_column(length));

<!----分割线---->

使用索引来排序
在排序操作中如果能使用到索引来排序,那么可以极大的提高排序的速度,要使用索引来排序需要满足以下两点即可。
1、ORDER BY子句后的列顺序要与组合索引的列顺序一致,且所有排序列的排序方向(正序/倒序)需一致
2、所查询的字段值需要包含在索引列中,及满足覆盖索引

索引排序有效的查询
在user_test表上创建一个组合索引
ALTER TABLE user_test ADD INDEX index_user(user_name , city , age);
可以使用到索引排序的案例
1、SELECT user_name, city, age FROM user_test ORDER BY user_name;
2、SELECT user_name, city, age FROM user_test ORDER BY user_name, city;
3、SELECT user_name, city, age FROM user_test ORDER BY user_name DESC, city DESC;
4、SELECT user_name, city, age FROM user_test WHERE user_name = 'feinik' ORDER BY city;

索引排序无效的查询
1、sex不在索引列中
SELECT user_name, city, age FROM user_test ORDER BY user_name, sex;
2、排序列的方向不一致
SELECT user_name, city, age FROM user_test ORDER BY user_name ASC, city DESC;
3、所要查询的字段列sex没有包含在索引列中
SELECT user_name, city, age, sex FROM user_test ORDER BY user_name;
4、where查询条件后的user_name为范围查询,所以无法使用到索引的其他列
SELECT user_name, city, age FROM user_test WHERE user_name LIKE 'feinik%' ORDER BY city;
5、多表连接查询时,只有当ORDER BY后的排序字段都是第一个表中的索引列(需要满足以上索引排序的两个规则)时,方可使用索引排序。如:再创建一个用户的扩展表user_test_ext,并建立uid的索引。
DROP TABLE IF EXISTS user_test_ext;
CREATE TABLE user_test_ext(
id int AUTO_INCREMENT PRIMARY KEY,
uid int NOT NULL,
u_password VARCHAR(64) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE user_test_ext ADD INDEX index_user_ext(uid);
走索引排序
SELECT user_name, city, age FROM user_test u LEFT JOIN user_test_ext ue ON u.id = ue.uid ORDER BY u.user_name;
不走索引排序
SELECT user_name, city, age FROM user_test u LEFT JOIN user_test_ext ue ON u.id = ue.uid ORDER BY ue.uid;