索引结构(BTreeB+Tree和Hash等)和分类(聚簇索引与非聚簇索引等)

Posted 龙鸣丿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了索引结构(BTreeB+Tree和Hash等)和分类(聚簇索引与非聚簇索引等)相关的知识,希望对你有一定的参考价值。

索引是数据结构,可以简单理解为排好序的快速查找数据结构

数据本身以外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构 的基础上实现高级查找算法,这种数据结构就是索引。

 

一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。

B-TREE: (B:balance)  会自动根据两边的情况自动调节,使两端无限趋近于平衡状态。可以使性能最稳定。(myisam使用的方式)

    B-TREE弊端:(插入/修改操作多时,B-TREE会不断调整平衡,消耗性能)从侧面说明了索引不是越多越好。

B+TREE:Innodb

 

索引的优势和劣势?

优势:提供数据的检索效率,降低数据库的IO成本。通过所以列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。

劣势:实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的。

虽然索引大大提高了查询速度,同事却会降低更新表的速度,如对表进行insert,update和delete。因为更新表时,mysql不仅要保存数据,还要保存以下索引文件,每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。

 

哪些情况需要创建索引?

1.主键自动建立索引

2.频繁作为查询条件的字段

3.查询中与其他表关联的字段,外键关系建立索引:

A 表关联 B 表:A join B  。  on 后面的连接条件 既 A 表查询 B 表的条件。所以 B 表被关联的字段建立索引能大大提高查询效率

因为在 join 中,join 左边的表会用每一个字段去遍历 B 表的所有的关联数据,相当于一个查询操作。

4.单键/组合索引的选择问题?(在高并发下倾向于创建组合索引)

5.查询中排序的字段

6.查询中统计或分组字段。

 

哪些情况不要创建索引?

表记录太少

经常增删改的表

where条件里用不到的字段不创建索引

数据重复且分布平均的表字段

 

索引的结构:

BTree索引:

【初始化介绍】

一颗b树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),

如磁盘块1包含数据项17和35,包含指针P1、P2、P3,

P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。

真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。

非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。

 

【查找过程】

如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。

真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。

 

B+Tree索引:

 

B+TREE 第二级的 数据并不能直接取出来,只作索引使用。在内存有限的情况下,查询效率高于 B-TREE

B-TREE 第二级可以直接取出来,树形结构比较重,在内存无限大的时候有优势。

 

B+Tree与B-Tree 的区别:结论在内存有限的情况下,B+TREE 永远比 B-TREE好。无限内存则后者方便

 

 1)B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+树叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。(一次查询可能进行两次i/o操作)

  2)在B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在而B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看B-树的性能好像要比B+树好,而在实际应用中却是B+树的性能要好些。因为B+树的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比B-树多,树高比B-树小,这样带来的好处是减少磁盘访问次数。尽管B+树找到一个记录所需的比较次数要比B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中B+树的性能可能还会好些,而且B+树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有文件,一个表中的所有记录等),这也是很多数据库和文件系统使用B+树的缘故。

 

思考:为什么说B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引?

1) B+树的磁盘读写代价更低

  B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。

2) B+树的查询效率更加稳定

  由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

 

聚簇索引与非聚簇索引:

关键点:(适用于InnoDB中主键构造成B+树,叶子节点存放表的记录数据,称为数据页。按照聚簇索引排列顺序,查询显示一定范围数据的时候,只需要查询比较少的数据页就可以了。

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式

术语‘聚簇’表示数据行和相邻的键值进错的存储在一起。

 如下图,左侧的索引就是聚簇索引,因为数据行在磁盘的排列和索引排序保持一致。

 

InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。这个特性决定了索引组织表中数据也是索引的一部分;

一般建表会用一个自增主键做聚簇索引,没有的话MySQL会默认创建,但是这个主键如果更改代价较高,故建表时要考虑自增ID不能频繁update这点。

MySQL数据库中innodb存储引擎,B+树索引可以分为聚簇索引(也称聚集索引,clustered index)和辅助索引(有时也称非聚簇索引或二级索引,secondary index,non-clustered index)。这两种索引内部都是B+树,聚集索引的叶子节点存放着一整行的数据。

Innobd中的主键索引是一种聚簇索引,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引。

Innodb使用的是聚簇索引,MyISam使用的是非聚簇索引。

聚簇索引的好处

按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多个数据块中提取数据,所以节省了大量的io操作。

由于行数据和叶子节点存储在一起同一页中会有多条行数据,访问同一数据页不同行记录时,已经把页加载到了Buffer中,再次访问的时候,会在内存中完成访问,不必访问磁盘。这样主键和行数据是一起被载入内存的,找到叶子节点就可以立刻将行数据返回了,如果按照主键Id来组织数据,获得数据更快。

辅助索引使用主键作为"指针"而不是使用地址值作为指针的好处是,减少了当出现行移动或者数据页分裂时辅助索引的维护工作,使用主键值当作指针会让辅助索引占用更多的空间,换来的好处是InnoDB在移动行时无须更新辅助索引中的这个"指针"。也就是说行的位置(实现中通过16K的Page来定位)会随着数据库里数据的修改而发生变化(前面的B+树节点分裂以及Page的分裂),使用聚簇索引就可以保证不管这个主键B+树的节点如何变化,辅助索引树都不受影响。

聚簇索引适合用在排序的场合,非聚簇索引不适合

取出一定范围数据的时候,使用用聚簇索引

二级索引需要两次索引查找,而不是一次才能取到数据,因为存储引擎第一次需要通过二级索引找到索引的叶子节点,从而找到数据的主键,然后在聚簇索引中用主键再次查找索引,再找到数据

可以把相关数据保存在一起。例如实现电子邮箱时,可以根据用户 ID 来聚集数据,这样只需要从磁盘读取少数的数据页就能获取某个用户的全部邮件。如果没有使用聚簇索引,则每封邮件都可能导致一次磁盘 I/O。

聚簇索引的限制:

对于mysql数据库目前只有innodb数据引擎支持聚簇索引,而Myisam并不支持聚簇索引。

由于数据物理存储排序方式只能有一种,所以每个Mysql的表只能有一个聚簇索引。一般情况下就是该表的主键。

为了充分利用聚簇索引的聚簇的特性,所以innodb表的主键列尽量选用有序的顺序id,而不建议用无序的id,比如uuid这种。(参考聚簇索引的好处。)

 

非聚簇索引:

将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因。

innodb中,在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值。

 

full-text全文索引:

全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用【分词技术】等多种算法智能分析出文本文字中关键词的频率和重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果。

 

CREATE TABLE `article` (

  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,

  `title` varchar(200) DEFAULT NULL,

  `content` text,

  PRIMARY KEY (`id`),

  FULLTEXT KEY `title` (`title`,`content`)

) ENGINE=MyISAM DEFAULT CHARSET=utf8;

不同于like方式的的查询:

SELECT * FROM article WHERE content LIKE ‘%查询字符串%’;

全文索引用match+against方式查询:

SELECT * FROM article WHERE MATCH(title,content) AGAINST (‘查询字符串’);

明显的提高查询效率。

限制:

mysql5.6.4以前只有Myisam支持,5.6.4版本以后innodb才支持,但是官方版本不支持中文分词,需要第三方分词插件。

5.7以后官方支持中文分词。

随着大数据时代的到来,关系型数据库应对全文索引的需求已力不从心,逐渐被 solr,elasticSearch等专门的搜索引擎所替代。

 

Hash索引

哈希算法

哈希算法时间复杂度为O(1),且不只存在于索引中,每个数据库应用中都存在该数据结构。

哈希表

哈希表也为散列表,又直接寻址改进而来。在哈希的方式下,一个元素k处于h(k)中,即利用哈希函数h,根据关键字k计算出槽的位置。函数h将关键字域映射到哈希表T[0...m-1]的槽位上。

 

图中哈希函数h有可能将两个不同的关键字映射到相同的位置,这叫做碰撞,在数据库中一般采用链接法来解决。在链接法中,将散列到同一槽位的元素放在一个链表中,如下图所示:

 

InnoDB存储引擎中的哈希算法

InnoDB中采用除法散列函数,冲突机制采用链接法。

 

BTree索引和哈希索引的区别

Hash索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位,不像B-Tree索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以Hash索引的查询效率要远高于B-Tree索引。

 

可能很多人又有疑问了,既然Hash索引的效率要比B-Tree高很多,为什么大家不都用Hash索引而还要使用B-Tree索引呢?任何事物都是有两面性的,Hash索引也一样,虽然Hash索引效率高,但是Hash索引本身由于其特殊性也带来了很多限制和弊端,主要有以下这些:

Hash索引仅仅能满足"=","IN"和"<=>"查询,不能使用范围查询哈希索引只支持等值比较查询,包括=、 IN 、<=>  (注意<>和<=>是不同的操作)。 也不支持任何范围查询,例如WHERE price > 100。  

由于Hash索引比较的是进行Hash运算之后的Hash值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的Hash算法处理之后的Hash值的大小关系,并不能保证和Hash运算前完全一样。

Hash索引无法被用来避免数据的排序操作。  

由于Hash索引中存放的是经过Hash计算之后的Hash值,而且Hash值的大小关系并不一定和Hash运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算;

Hash索引不能利用部分索引键查询。  

对于组合索引,Hash索引在计算Hash值的时候是组合索引键合并后再一起计算Hash值,而不是单独计算Hash值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash索引也无法被利用。

Hash索引在任何时候都不能避免表扫描。  

前面已经知道,Hash索引是将索引键通过Hash运算之后,将 Hash运算结果的Hash值和所对应的行指针信息存放于一个Hash表中,由于不同索引键存在相同Hash值,所以即使取满足某个Hash键值的数据的记录条数,也无法从Hash索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果。

Hash索引遇到大量Hash值相等的情况后性能并不一定就会比BTree索引高。 

对于选择性比较低的索引键,如果创建Hash索引,那么将会存在大量记录指针信息存于同一个Hash值相关联。这样要定位某一条记录时就会非常麻烦,会浪费多次表数据的访问,而造成整体性能低下。

 

MySQL索引分类:

主键索引:

设定为主键后数据库会自动建立索引,innodb为聚簇索引。

语法:

随表一起建索引:

CREATE TABLE customer (id INT(10) UNSIGNED  AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200),

  PRIMARY KEY(id)

);

unsigned (无符号的)

使用  AUTO_INCREMENT 关键字的列必须有索引(只要有索引就行)。

CREATE TABLE customer2 (id INT(10) UNSIGNED   ,customer_no VARCHAR(200),customer_name VARCHAR(200),

  PRIMARY KEY(id)

);

 单独建主键索引:

ALTER TABLE customer

 add PRIMARY KEY customer(customer_no); 

删除建主键索引:

ALTER TABLE customer

 drop PRIMARY KEY ; 

修改建主键索引:

必须先删除掉(drop)原索引,再新建(add)索引

 

单值索引:

即一个索引只包含单个列,一个表可以有多个单列索引。

索引建立成哪种索引类型?

根据数据引擎类型自动选择的索引类型

除开 innodb 引擎主键默认为聚簇索引 外。 innodb 的索引都采用的 B+TREE

myisam 则都采用的 B-TREE索引

语法

随表一起建索引:

CREATE TABLE customer (id INT(10) UNSIGNED  AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200),

  PRIMARY KEY(id),

  KEY (customer_name) 

);

 随表一起建立的索引 索引名同 列名(customer_name)

单独建单值索引:

CREATE  INDEX idx_customer_name ON customer(customer_name);

删除索引:

DROP INDEX idx_customer_name ;

 

唯一索引:

索引列的值必须唯一,但允许为空。

语法:

随表一起建索引:

CREATE TABLE customer (id INT(10) UNSIGNED  AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200),

  PRIMARY KEY(id),

  KEY (customer_name),

  UNIQUE (customer_no)

);

建立 唯一索引时必须保证所有的值是唯一的(除了null),若有重复数据,会报错。 

单独建唯一索引:

CREATE UNIQUE INDEX idx_customer_no ON customer(customer_no);

删除索引:

DROP INDEX idx_customer_no on customer ;

 

复合索引:即一个索引包含多个列

在数据库操作期间,复合索引比单值索引所需要的开销更小(对于相同的多个列建索引)

当表的行数远大于索引列的数目时可以使用复合索引

语法:

随表一起建索引:

CREATE TABLE customer (id INT(10) UNSIGNED  AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200),

  PRIMARY KEY(id),

  KEY (customer_name),

  UNIQUE (customer_name),

  KEY (customer_no,customer_name)

);

单独建索引:

CREATE  INDEX idx_no_name ON customer(customer_no,customer_name);

删除索引:

DROP INDEX idx_no_name  on customer ;

 

以上是关于索引结构(BTreeB+Tree和Hash等)和分类(聚簇索引与非聚簇索引等)的主要内容,如果未能解决你的问题,请参考以下文章

Mysql Hash索引和B-Tree索引区别(Comparison of B-Tree and Hash Indexes)

MySQL索引 B+tree和hash

B-Tree 索引和 Hash 索引的对照

MySQL Hash索引和B-Tree索引的区别

btree索引和hash索引的区别

MySQL的btree索引和hash索引的区别