MySql BTree和Hash索引的比较,为什么InnoDB不使用Hash索引

MYSQL 索引分析

MySql BTree和Hash索引的比较,为什么InnoDB不使用Hash索引_第1张图片

MySql BTree和Hash索引的比较,为什么InnoDB不使用Hash索引_第2张图片
关于Btree能存储多少数据的分析

-1 为啥要用B+Tree而不用btree?

btree所有的节点都存储数据,而b+tree只有叶子节点才存储数据
而内存每次加载数据的大小是有限的,而有数据的时候,加载的量就会很小,如果都是索引,就会加载更多的索引值

0. B+Tree能存多少数据

mysql> show global status like 'Innodb_page_size';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Innodb_page_size | 16384 |
+------------------+-------+
1 row in set
  1. innodb的所有数据文件.idb大小都是16K即16384的整数倍
  2. 我们的InnoDB页的大小默认是16k?为啥是16K?因为16K的话,高度为3的树就可以存储千万级别的数据,如下有说明
  3. 假设一行数据的大小是1k,那么一个页可以存放16行这样的数据。
  4. 我们假设主键ID为bigint类型,长度为8字节,而指针大小在InnoDB源码中设置为6字节,这样一共14字节
  5. 我们一个页中能存放多少这样的单元,其实就代表有多少指针,即16384/14=1170。
  6. 那么可以算出一棵高度为2的B+树,即存在一个根节点和若干个叶子节点,那么这棵B+树的存放总记录数为:根节点指针数单个叶子节点记录行数。能存放117016=18720条这样的数据记录。
  7. 根据同样的原理我们可以算出一个高度为3的B+树可以存放:1170117016=21902400条这样的记录。

所以在InnoDB中B+树高度一般为1-3层,它就能满足千万级的数据存储。
在查找数据时一次页的查找代表一次IO,所以通过主键索引查询通常只需要1-3次IO操作即可查找到数据。

1.B+Tree
之所以Btree能加快访问数据的速度,是因为存储引擎不再需要进行全表扫描
只需要从索引的根节点开始进行搜索,根节点的槽中存放了指向子节点的指针,
存储引擎会根据这些指针向下层查找,而这些指针实际上定义了子节点页中值的上限和下线

比如:

create table people(
xing varchar(20) not null,
ming varchar(20) not null,
birthday date not null,
gender enum('male','unknow','female') not null,
key(last_name ,first_name,birthday)
)

索引如果查询多条数据,也是可以排序的


2. BTREE索引使用的限制
1. 如果不是按照最左列开始查找,无法使用索引
	例如无法用于查找  ming为XXX的,也无法查找 birthday为XXX的
ABC三个组成的索引,只能A 、AB、 ABC
其它的组合都是无法用到索引的
2. 如果中间有一列是范围查询,则之后的列不能用于索引查询
比如
WHERE A =XXX AND B like 'XXX%' and C =XXX
这个索引只能用到前两列

HASH索引


在InnoDB上面创建HASH索引


我们利用

先查询当前数据库的版本
mysql> select  version();
查询当前实际表结构
show create table peopleHash ;


mysql> show create table peopleHash ;

| peopleHash | CREATE TABLE `peoplehash` (
  `xing` varchar(20) COLLATE utf8mb4_general_ci NOT NULL,
  `ming` varchar(20) COLLATE utf8mb4_general_ci NOT NULL,
  `birthday` date NOT NULL,
  `gender` enum('male','unknow','female') COLLATE utf8mb4_general_ci NOT NULL,
  KEY `ming` (`ming`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci |
2. 1 为什么在InnoDB创建HASH索引失败?
结果并没有出现我们想要的USING HASH ,也就是说创建HASH索引失败
为什么呢?

下面是MYSQL官方的回答:
1.不支持HASH索引(但是InnoDB在内部利用哈希索引来实现其自适应哈希索引功能。)

2.也就是InnoDB会根据表的使用情况自动为表生成hash索引,不能人为干预是否在InnoDB一张表中创建HASH索引

3.或者说,如果InnoDB注意到某些索引值被使用的特别频繁时,
它会在内存中基于Btree的索引之上再创建一个HASH索引,这样BTREE索引也具备了HASH索引的一些优点

但是也没有支持HSAH索引的引擎呢?

答案是肯定的:
MEMORY引擎是支持的
2.2 为什么InnoDB和MyISAM引擎不支持HASH索引?
1. HASH索引本身只存储对应的HASH值和行指针,而不是存储字段值
2. HASH索引并不是按照索引顺序来存储的,因此无法排序
3. HASH索引不支持部分索引列查找,因为HASH索引是使用全部的内容来计算HASH值的
	例如在(A,B)两列建立索引,只查询A无法使用索引
4.HASH索引只支持等值比较查询,包括 =、IN()不能进行任何的范围查询
	
HASH冲突
5. 最严重的是既然HASH值是数字,肯定会出现相同的,也就是HASH冲突
6. 出现HASH冲突的时候,存储引擎必须遍历链表中所有的行指针,逐行进行比较,直到找到所有符合条件的行
7. 当HASH冲突特别多的时候,维护操作的成本就会变大,比如一次数据的删除
引擎需要遍历对应HASH值链表上的每一行,找到并删除对应的引用,冲突越多,代价越大

3. 模拟一个HASH索引

比如只有两个字段 ID  和URL
我们想查询URL是什么的  select id from XXX where url ='http://XXXXXXXX'
如果用BTREE来存储URL,则需要用到大量的空间,那我们还想用索引,怎么办呢?
删除URL原来的索引,新增一个字段  url_crc  使用CRC32做HASH
select id from XXX where url ='http://XXXXXXXX' and url_crc =CRC32('http://XXXXXXXX')
1.只要在插入和更新数据的时候,对url_crc进行更新就可以了
2. 但是如果用这个方式,记住不要用SHA1() MD5()作为HASH函数
3. 因为那两个函数的hash值非常长,会浪费大量的空间,但是如果数据量非常大,CRC32()会出现大量冲突
4. 自定义64位的哈希函数
	简单的做法是:用MD5()函数返回值的一部分作为自定义哈希函数
select CONV(RIGHT(MD5('http://XXXXXXX'),16),16,10)AS HASH64;
插入和更新的时候都可以用这个了
insert into XXX 
(id,url,url_crc) values 
(XXX,XXX ,CONV(RIGHT(MD5('http://XXXXXXX'),16),16,10));

4. 索引是最好的解决方案吗

当然不是
1.对于数据量特别小的表,全表扫描更高效,没必要索引
2.中到大型的表,索引很高效,没有的话,速度很低
3.超大型表,由于数据量大,索引使用的代价会很大,这种情况下可以使用
	分区技术

5. 索引的分类

  1. MYISAM引擎,非聚集索引
    因为索引文件myi和数据文件myd是分开的,myi里面存储的是myd文件的指针,而且主键索引和非主键索引没有层次关系,因为存储的都是指向myd文件的指针
  2. Innodb引擎是聚集索引
    只有一个idb文件,在主键索引和非主键索引有层次的关系,因为主键索引存储的是真是的信息,而非主键索引存储的是主键的信息
6. 创建索引的原则
  1. 列的离散型:
    离散型的计算公式:count(distinct col):count(col),离散型越高,选择型越好。
    比如性别字段,只有男女、未知等在索引查询的时候其实还不如全表扫描,因为在索引创建的时候,如果此时检索 sex = 1的数据,根节点判断的时候,结果是查询左子树,但是当在左子树第二层再进行判断的时候,因为左右分支都满足条件,所以很难抉择选择哪一个分支继续搜索,或者是把两个分支同时进行搜索。如图:
    a
  2. 最左匹配原则并且优先创建联合索引原则
    这个很多介绍,不必说了
  3. 覆盖索引:
    假如teacherNo 使用到了索引,检索到teacherNo 时候,可以直接将索引中的teacherNo 值返回,不需要进入数据区。
Select teacherNo from teacher where teacherNo = ?
7. mysql为啥不推荐用UUID
  1. UUID字段很长,而且不是int类型的,存储的数据量就会大大减少,索引效率不高
  2. UUID因为不是自增的,在插入索引的时候,会对原有的叶片就行维护并分裂的成本特别高
  3. 比如是自增的iD,我们看下是怎么进行分裂的,在下图中插入7,并且规定一个一片中只能插入4个记录
    MySql BTree和Hash索引的比较,为什么InnoDB不使用Hash索引_第3张图片
    插入记录7,由于叶页面中只能存放4条记录,插入记录7,导致叶页面分裂,产生一个新的叶页面。
  4. 传统B+树页面分裂操作分析:

按照原页面中50%的数据量进行分裂,针对当前这个分裂操作,3,4记录保留在原有页面,5,6记录,移动到新的页面。最后将新纪录7插入到新的页面中;
50%分裂策略的优势:
分裂之后,两个页面的空间利用率是一样的;如果新的插入是随机在两个页面中挑选进行,那么下一次分裂的操作就会更晚触发;
50%分裂策略的劣势:
空间利用率不高:按照传统50%的页面分裂策略,索引页面的空间利用率在50%左右;
分裂频率较大:针对如上所示的递增插入(递减插入),每新插入两条记录,就会导致最右的叶页面再次发生分裂;
5. 传统50%分裂的策略,有不足之处,如何优化?接着往下看。
6. B+树分裂操作的优化?
由于传统50%分裂的策略,有不足之处,因此,目前所有的关系型数据库,包括Oracle/InnoDB/PostgreSQL,都针对B+树索引的递增/递减插入进行了优化。经过优化,以上的B+树索引,在记录6插入完毕,记录7插入引起分裂之后,新的B+树结构如下图所示:

对比上下两个插入记录7之后,B+树索引的结构图,可以发现二者有很多的不同之处:

新的分裂策略,在插入7时,不移动原有页面的任何记录,只是将新插入的记录7写到新页面之中;
原有页面的利用率,仍旧是100%;
优化分裂策略的优势:
索引分裂的代价小:不需要移动记录;
索引分裂的概率降低:如果接下来的插入,仍旧是递增插入,那么需要插入4条记录,才能再次引起页面的分裂。相对于50%分裂策略,分裂的概率降低了一半;
索引页面的空间利用率提高:新的分裂策略,能够保证分裂前的页面,仍旧保持100%的利用率,提高了索引的空间利用率;
优化分裂策略的劣势:
如果新的插入,不再满足递增插入的条件,而是插入到原有页面,那么就会导致原有页面再次分裂,增加了分裂的概率。

8. mysql索引究竟如何插入的呢?

官方解读

你可能感兴趣的:(MySql)