数据库索引
数据库索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息,索引的目的是为了加快检索表中数据。索引的实现通常使用 B树 及其变种 B+树。
数据库索引
原理:通过不断的缩小想要获得数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是我们总是通过同一种查找方式来锁定数据。
优缺点
优点:
- 创建唯一索引,保证数据库表中每行数据的唯一性
- 加快数据的检索速度
- 加速表与表之间的连接,特别是在实现数据的参考完整性方面特别有意义
- 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间
- 通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能
缺点:
- 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加
- 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大
- 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,降低了数据的维护速度
应用场景
适用于建立索引的情况:主键、经常搜索、连接(外键)、排序的、where 的、统计或分组的
- 建立在经常需要搜索的字段上,可以加快搜索的速度;
- 在作为主键的字段上,强制该列的唯一性和组织表中数据的排列结构;
- 经常与其他表进行连接的字段,这些字段主要是外键,可以加快连接的速度;
- 经常需要根据范围进行搜索的字段上创建索引,因为索引已经排序,其指定的范围是连续的;
- 在经常需要排序的字段上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;
- 经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。
不适用于建立索引的情况:
- 查询中很少使用或者参考的字段不应该创建索引。
- 数据值选择性很低的字段,不适合建索引,比如性别
- 对于大的文本字段甚至超长字段,不要建索引,比如text, image 和 bit 数据类型
- 修改操作远远多于检索操作时,不应该创建索引。
- 频繁更新(增删改)的字段不适合创建索引
mysql 创建索引的方式
在执行 CREATE TABLE 语句时可以创建索引,也可以单独用 CREATE INDEX 或 ALTER TABLE 来为表增加索引。
在创建表时建立索引
1
2
3
4
5
6
7
8
9CREATE TABLE student(
sno INT AUTO_INCREMENT
sname VARCHAR(20)
birth DATE
PRIMARY KEY(sno) # 创建主键索引
UNIQUE INDEX [sname_index] (sname) # 创建唯一索引,也可以在上面字段定义时使用 UNIQUE 来创建
INDEX [sname_birth_index] (sname, birth) # 创建普通的组和索引
)CREATE [UNIQUE | CLUSTERED] INDEX index_name ON table_name(column_name1 ASC, column_name2 DESC)
注意: create index 不能创建主键索引1
ALTER TABLE table_name ADD [UNIQUE|CLUSTERED] INDEX [index_name] (column name)
alter table 可以创建主键索引: ALTER TABLE student ADD PRIMARY KEY (sno)
删除索引
- DROP INDEX index_name ON table_name
- ALTER TABLE table_name DROP INDEX index_name
- ALTER TABLE table_name DROP PRIMARY KEY
查看索引
SHOW INDEX FROM database_name.table_name;
聚簇索引与非聚簇索引
聚簇索引:物理索引,与基表的物理顺序相同,数据值的顺序总是按照顺序排列。一般情况下主键会默认创建聚簇索引,在一张表上最多只能创建一个聚集索引。直接缩小范围找到记录。
1 | CREATE CLUSTERED INDEX my_index ON student(id) |
非聚簇索引: 表数据存储顺序与索引顺序无关,需要通过位置去找到记录。
1 | CREATE UNCLUSTERED INDEX my_index ON student(my_column) |
唯一索引与主键索引
唯一索引: 保证在索引列中的全部数据是唯一的,对聚簇索引和非聚簇索引都可以使用,允许有空值
1 | CREATE UNIQUE CLUSTERED INDEX my_index ON student(my_column) |
主键索引: 就是主键,是一种特殊的唯一索引,不能为空。一张表中只能定义一个主键索引,通常有一列或列组合,用于唯一标识一条记录,使用关键字 PRIMARY KEY 来创建。为表定义一个主键将自动创建主键索引(聚簇索引)。主键可以是聚簇索引也可以是非聚簇索引。
区别:
- 主键索引一定是唯一索引,唯一索引不一定是主键索引
- 唯一索引可以为空,主键索引不能为空
- 一张表只能有一个主键索引,可以有多个唯一索引
索引失效的情况
- 如果条件中有 or,即使其中有部分条件带索引也不会使用。 要想使用 or,又想让索引生效,只能将 or 条件中的每个列都加上索引。
- like 查询是以 % 开头,索引失效;但以 % 结尾,索引可以使用
- 存在索引列的数据类型隐形转换,则用不上索引,比如列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
- 对于多列索引,必须满足 最左匹配原则 (eg:多列索引col1、col2和col3,则 索引生效的情形包括 col1或col1,col2或col1,col2,col3)。
- where 子句里对索引列上有数学运算,用不上索引
- where 子句里对有索引列使用函数,用不上索引, where abs(id)=1
- 如果 mysql 估计使用全表扫描要比使用索引快,则不使用索引,比如数据量极小的表
最左匹配原则
一般把排序分组频率最高的列放在最左边。
如果在一个表的三列,col1,col2,col3 建立了一个联合索引,在 select 语句中:
- where col1,where col1 and col2, where col1 and col2 and col3,可以命中索引
- where col2,where col3,不会命中索引
- where col1 and col3,能够命中部分索引,也就是 col1 这部分
- where col2 and col1,先会拿 col2 去比较,没有结果,但是 mysql 会对这个语句进行优化,把 col1 放在第一位,因此可以命中索引
索引的实现原理
为什么加索引能够优化慢查询:
DB 在执行一条 Sql 语句的时候,默认的方式是根据搜索条件进行全表扫描,遇到匹配条件的就加入搜索结果集合。如果我们对某一字段增加索引,查询时就会先去索引列表中一次定位到特定值的行数,大大减少遍历匹配的行数,所以能明显增加查询的速度。
索引其实是一种优化查询的数据结构,比如 Mysql 中的索引是用 B+ 树实现的,而 B+ 树就是一种能够优化查询速度的数据结构,可以利用索引快速查找数据,所以能优化查询。
能够优化查询速度的数据结构: B 树、 B+ 树、 哈希表、 平衡二叉树等
B-树
B-树(Balance tree),也叫 B树,是一种平衡的多叉树,又称平衡多路查找树或外部查找树。
满足条件:
- 任意非叶子节点最多只有 M 个儿子节点,且 M > 2
- 根节点的儿子数为 [2, M]
- 除根节点外的非叶子节点的儿子数为 [M/2, M]
- 每个节点存放至少 M/2-1(取上整)和至多 M-1 个关键字(至少2个关键字)
- 非叶子节点的关键字个数 = 指向儿子的指针个数 - 1
- 非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
- 非叶子结点的指针:P[1], P[2], …, P[M];其中 P[1] 指向关键字小于 K[1] 的子树,P[M] 指向关键字大于 K[M-1] 的子树,其它 P[i] 指向关键字属于 (K[i-1], K[i]) 的子树;
- 所有叶子结点位于同一层;
如图,一棵 M=3 的 B-树
B-树的特性:
- 关键字集合分布在整颗树中;
- 任何一个关键字出现且只出现在一个结点中;
- 搜索有可能在非叶子结点结束;
- 其搜索性能等价于在关键字全集内做一次二分查找;
- 自动层次控制;
B-树的查找性能为 O(logn), n 为关键词总数, 与 M 无关。
B+树
B+树是B-树的变体,sqlite、mysql 应用 B+树处理索引。
B+树与B-树的定义基本相同,除了以下几点:
- 非叶子结点的子树指针与关键字个数相同
- 非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树,(注意B-树是开区间)
- 所有叶子结点增加一个链指针,构成一个有序链表,可以按照关键码排序的次序遍历全部记录。
- 所有关键字都在叶子结点出现,非叶子节点仅具有索引作用
如图,一棵 M=3 的 B+树
B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找;
B+树和B-树的优点
B+ 树的优点:
- 由于B+树在内部节点上不包含数据信息,因此在内存页中能够存放更多的 key。 数据存放的更加紧密,具有更好的空间局部性。因此访问叶子节点上关联的数据也具有更好的缓存命中率。
- B+树的叶子结点都是相链的,因此对整棵树的便利只需要一次线性遍历叶子结点即可。而且由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。
B-树的优点:
由于B树的每一个节点都包含 key 和 value,因此经常访问的元素可能离根节点更近,因此访问也更迅速。
B+树和B-树的区别
关键字的数量不同;B+树中分支结点有m个关键字,其叶子结点也有m个,其关键字只是起到了一个索引的作用,但是B树虽然也有m个叶子结点,但是其只拥有m-1个关键字。
存储的位置不同;B+树中的数据都存储在叶子结点上,也就是其所有叶子结点的数据组合起来就是完整的数据,但是B树的数据存储在每一个结点中,并不仅仅存储在叶子结点上。
分支结点的构造不同;B+树的分支结点仅仅存储着关键字信息和儿子的指针(这里的指针指的是磁盘块的偏移量),也就是说内部结点仅仅包含着索引信息。
查询不同;B树在找到具体的数值以后,则结束,而B+树则需要通过索引找到叶子结点中的数据才结束,也就是说B+树的搜索过程中走了一条从根结点到叶子结点的路径。
数据库索引为何不用二叉树
考虑到磁盘 IO 的影响,它相对于内存来说是很慢的。
数据库索引是存储在磁盘上的,当数据量大时,就不能把整个索引全部加载到内存了,只能逐一加载每一个磁盘页(对应索引树的节点)。
所以我们要减少 IO 次数,对于树来说,IO 次数就是树的高度,而“矮胖”就是b树的特征之一,它的每个节点最多包含m个孩子,m称为b树的阶,m的大小取决于磁盘页的大小。
为什么 mysql 要用 B+树
mysql 中的数据是存放在磁盘中的,读取数据需要对磁盘进行访问,数据量太大的话无法一次性加载到内存中,而使用 B树或B+树就只需加载部分索引。局部性原理与磁盘预读
预读: 即使只需要一个字节,操作系统也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这里的一定长度叫做页,也就是操作系统操作磁盘时的基本单位。一般操作系统中一页的大小是4Kb。
磁盘预读
当一个数据被用到时,其附近的数据也通常会马上被使用。 为了减少磁盘 I/O 操作,磁盘往往不是严格按需读取,而是每次都会预读。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的磁盘旋转时间,速度会非常快。
操作系统一般将内存和磁盘分割成固定大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点。并且可以利用预读特性,相邻的节点也能够被预先载入。
不用 AVL 或 红黑树的原因是:当数据量比较大时,都会由于树的深度过大而造成 IO 读写过于频繁,进而导致查询效率低下。而B+树的操作能够保持较低的高度,从而保证高效的查找效率。
不用 B 树的原因是:
B+树的磁盘读写代价更低:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。
B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
数据库索引采用B+树而不是B树的主要原因:B+树只要遍历叶子节点就可以实现整棵树的遍历,方便扫库,而且在数据库中基于范围的查询是非常频繁的,而B树只能中序遍历所有节点,效率太低。
对于多条数据的查找,B树要做局部的中序遍历,还可能要跨层访问;而B+树所有数据都在叶子节点,不用跨层,且有链表结构,只需要找到首尾通过链表就能读出所有数据。
哈希索引
哈希索引能以 O(1) 时间进行查找,但是失去了有序性:
- 无法用于排序与分组;
- 只支持精确查找,无法用于部分查找和范围查找。
InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。
用 B+树查找的时间为 O(logn),用 hash 查找的时间为 O(1),那索引实现为什么要用 B+树而不是 hash 呢?
答: 与业务场景相关
- 如果只查找一个值的话,hash 是一个很好的选择,而数据库经常会选择多条,这时候由于B+树索引有序,并且又有链表相连,它的查询效率比 hash 就快得多。
- 数据库中的索引一般是在磁盘上,数据量大的情况可能无法一次装入内存,B+树的设计可以允许数据分批加载,同时树的高度较低,能够提高查找效率。
MyIASM 和 InnoDB 的索引结构
MyIASM
MyISAM 引擎的索引结构为 B+树,其中B+树的数据域存储的内容为实际数据的地址,也就是说它的索引和实际的数据是分开的,索引文件仅保存数据记录的地址,只不过是用索引指向了实际的数据,这种索引就是非聚集索引。
在 MyISAM 中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求 key 是唯一的,而辅助索引的 key 可以重复。
InnoDB
InnoDB 引擎的索引结构也是 B+树,但是 Innodb 的索引文件本身就是数据文件,即B+树的数据域存储的就是实际的数据,这种索引是聚集索引。这个索引的 key 就是数据表的主键,因此 InnoDB 表数据文件本身就是主索引。
InnoDB 的辅助索引数据域存储的也是相应记录主键的值而不是地址,所以当以辅助索引查找时,会先根据辅助索引找到主键,再根据主键索引找到实际的数据。
InnoDB 不建议使用过长的主键,否则会使辅助索引变得过大。建议使用自增的字段作为主键,这样B+树的每一个结点都会被顺序的填满,而不会频繁的分裂调整,会有效的提升插入数据的效率。
InnoDB 数据文件本身是一颗B+树。
B+ 树一个节点的大小: Mysql的Innodb引擎中一页的默认大小是16k(如果操作系统中一页大小是4k,那么Mysql中1页=操作系统中4页)
B+ 树一个节点存储的内容:
非叶子节点: 索引 + 指针
叶子节点: 数据