数据库——一文解析MySQL索引机制

一、前言

本文介绍MySQL索引机制,分为四个部分:“索引引入及其优点”、“B+树索引”、“哈希索引”、“全文索引”,一起来看看吧!

1.1 索引引入

索引(在 MySQL中也叫做“键(key)")是存储引擎用于快速找到记录的一种数据结构,这是索引的基本功能。索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对性能的影响愈发重要,在数据量较小且负载较低时,不恰当的索引对性能的影响可能还不明显,但当数据量逐渐增大时,性能则会急剧下降。
但是,索引却经常被忽略,有时候甚至被误解,所以在实际案例中经常会遇到由糟糕索引导致的问题。索引优化应该是对查询性能优化最有效的手段了,索引能够轻易将查询性能提高几个数量级,“最优”的索引有时比一个“好的”索引性能要好两个数量级。
例如:

如果在Dep_id列上建有索引,则 MySQL将使用该索引找到Dep_id为5的行,也就是说, MySQL先在索引上按值进行查找,然后返回所有包含该值的数据行索引可以包含一个或多个列的值,如果索引包含多个列,那么列的顺序也十分重要,因为MySQL只能高效地使用索引的最左前缀列。

创建一个包含两个列的索引,和创建两个只包含一列的索引是大不相间的,下面将详细介绍.

如果使用的是ORM,是否还需要关心索引?
回答:是的,仍然需要关心索引,即使是使用对象美系映射(ORM)工具也要关心索引。
ORM工具能够生产符合逻辑的,合法的查询(多数时候),除非只是生成非常基本的查询(例如仅是根据主键查询),否则它很难生成适合索引的查询。无论是多么复杂的ORM工具,在精妙和复杂的索引面前都是"浮云"。读完本文后面的内容,你就会同意这个观点的!很多时候,即使是查询优化技术专家也很难兼顾到各种情况,更别说ORM了。

索引有很多种类型,可以为不同的场景提供更好的性能,在 MySQL中,索引是在存储 引擎层而不是服务器实现的。所以,并没有统一的索引标准:不同存储引擎的索引的 工作方式并不一样,也不是所有的存储引擎都支持所有类型的索引。即使多个存储引擎支持同一种类型的索引,其底层的实现也可能不同。常用的索引有三种:B+树索引、哈希索引、全文索引,本文分别在第二部分、第三部分、第四部分介绍。

1.2 索引优点

索引可以让服务器快速地定位到表的指定位置。但是这井不是索引的唯一作用,到目前 为止可以看到,根据创建索引的数据结构不同,索引也有一些其他的附加作用。

最常见的B-Tree索引,按照顺序存储数据,所以MySQL可以用来做ORDER BY和GRUP BY操作。因为数据是有序的,所以B-Tree也就会将相关的列值都存储在一起。最后,因为索引中存储了实际的列值,所以某些查询只使用索引就能够完成全部查询。据此特性,总结下来索引有如下三个优点:
1.索引大大减少了服务器需要扫描的数据量;
2.素引可以帮助服务器避免排序和临时;
3.素引可以将随机LO变为版序1O;

索引是最好的解决方案吗?
索引并不总是最好的工具,总的来说,只有当素引帮助存储引学快速查找到记录带来的好处大于其带来的额外工作时,常引才是有效的,对于非常小的表,大部分情况下简单的全表扫描更高效。对于中到大型的表,索引就非常有效。但对于特大型的表,建立和使用索引的代价将随之增长。这种情况下,则需要一种技术可以直接区分出查询需要的一组数据,而不是一条记录一条记录地匹配,例如可以便用分区战术。
如果表的数量种多,可以建立一个元数据信息表,用来查询需要用到的某些特性。例如执行那些需要聚合多个应用分布在多个表的数据的查询,则需要记录“哪个用户的信息存储在哪个表中”的元数据,这样在查询时就可以直接忽略那些不包含指定用户信息的表。对于大型系统,这是一个常用的技巧,事实上, Infobright就是使用类似的实现。对于TB级别的数据,定位单条记录的意义不大,所以经常会使块级别元数据技术来替代索引。

二、B+树索引

2.1 从B树到B+树

2.1.1 B树

B树(B tree)是一种平衡的多路查找树,主要面向动态查找,通常用在文件系统中。

1、B树引入

一棵m阶的B-树或者为空树,或者为满足下列特性的m叉树
(1)所有的叶子结点都出现在同一层,并且不带信息。叶子结点的双亲称为终端结点;
(2)树中每个结点至多有m棵子树;
(3)若根结点不是终端结点,则至少有两棵子树;
(4)除根结点之外的所有非终端结点至少有⌈m/2⌉棵子树;
(5)所有的非终端结点都包含以下数据: (n,A0,K1,A1,K2,…,Kn,An)
其中,n(⌈m/2⌉-1≤n≤m-1)为关键码的个数,Ki(1≤i≤n)为关键码,且Ki<K(i+1)(1≤i≤n-1),Ai(0≤i≤n)为指向子树根结点的指针,且指针Ai所指子树中所有结点的关键码均小于K(i+1)大于Ki。
一般情况下,B树的叶子结点可以看做是外部结点(即查找失败)的结点,通常称为外结点。实际上这些结点不存在,指向这些结点的指针为空。所以,B树的叶子可以不画出来。因为叶子都出现在同一层上,所以B树也是树高平衡的。另外,每个结点中关键码的个数为子树的个数减1。
B树是2-3树的推广,2-3树是一个3阶B树。通常B树中的一个结点的大小能够填满一个磁盘页,存储在B树中的指针实际上是包含其孩子结点的块号,每个结点一般允许100个或者更多个孩子。

2、B树查找

B.树的查找类似于2-3树的查找,所不同的是B树的每个结点上是多关键码的有序表,在到达某个结点时,先在有序表中查找,若找到,则查找成功;否则,按照指针信息到相应的子树中查找。当到达叶子结点时,则说明树中没有对应的关键码,查找失败。在

B树上的查找过程是一个顺指针查找结点和在结点中查找关键码交叉进行的过程。比如,上图中查找关键码为53的记录。首先,从root指向的根结点a开始,根结点a 中只有一个关键码,且53大于它,因此,按根结点a的指针域A1到结点c去查找,c结点有两个关键码(43、78),而53大于43小于78,应按结点c指针域A1到结点g去查找,在结点g中顺序比较关键码,找到关键码53。
所以,在B树上进行查找包含两种基本操作:(1)在B树中查找结点;(2)在结点中查找关键码。由于B树通常存储在磁盘上,则前一个查找操作(指在B树中查找结点)是在磁盘上进行的,而后一个查找操作(指在结点中查找关键码)是在内存中进行的,即在磁盘上找到某结点后,先将结点的信息读入内存,然后再查找等于k的关键码。显然,在磁盘上进行一次查找比在内存中进行一次查找耗费的时间多得多,因此,在磁盘上进行查找的次数,即待查关键码所在结点在B树的层数,是决定B树查找效率的首要因素。

3、B树插入

B树的插入是2-3树插入的推广
假定要在m阶B树中插入关键码key,设n=m-1,即n为结点中关键码数目的最大值,B树的插入过程如下:
(1)定位:查找插人位置。由于是在终端结点中插入,因此要确定它属于哪个终端结点。定位的结果是返回了key所属终端结点的指针p。若p中的关键码个数小于n,则 直接插人关键码key;否则,结点p的关键码个数溢出,执行“分裂一提升”过程。
(2)分裂一提升:将结点p“分裂”成两个结点,分别是p1和p2,把中间的关键码k“提升”到父结点,并且k的左指针指向p1,右指针指向p2。如果父结点的关键码个数也溢出,则继续执行“分裂一提升”过程。显然,这种分裂可能一直上传,如果根结点也分裂了,则树的高度增加了一层 。
这个插入过程保证所有的结点至少是半满的。例如,当一个4阶B树的内部结点已满时,将会有5个子女。这个结点分裂成为两个结点,每个结点包含两个关键码,这样就 保持了B树的特性。下图给出了在3阶B树中进行插入的示例:

4、B树删除

B树的删除是2-3树删除的推广。
设在m阶B树中删除关键码key。首先要找到key的位置,即“定位”。定位的结果是返回了key所在结点的指针q,假定key是结点q中第i个关键码K,若结点q不是终结点,则用Ai所指的子树中的最小键值x来“替换”Ki。由于x所在结点一定是终端结点,这样,删除问题就归结为在终端结点中删除关键码。
如果终端结点中关键码的个数大于⌈m/2⌉-1,则可直接删除该关键码,如下图:

如果在终端结点中删除一个关键码后其关键码的个数不足⌈m/2⌉-2,则不符合m阶B树的要求,需要从兄弟结点借关键码或合并结点,以保证B树的特性。具体分两种情况:
(1)兄弟够借,查看相邻的兄弟结点,如果兄弟结点有足够多的记录(多于⌈m/2⌉),就从兄弟结点借来一个记录,将借来的关键码“上移”到被删结点的双亲结点中,同时将双亲结点中的相应关键码“下移”到被删结点中。这样做的目的是为了尽可能地延迟由于删除而引起的结点中关键码个数的下溢。
(2)兄弟不够借。如果没有一个兄弟结点可以把记录借给这个记录太少的被删结点,那么被删结点就必须把它的关键码让给一个兄弟结点,即执行“合并”操作,并且从树中把这个空结点删除。兄弟结点当然有空间,因为兄弟结点至多半满,合并后被删结点的双亲少了一个结点,所以要把双亲结点中的一个关键码“下移”到合并结点中。如果被删结点的双亲结点中的关键码的个数没有下溢,则合并过程结束;否则,双亲结点也要进行借关键码或合并结点。显然,合并过程可能会上传到根结点,如果根结点的两个子女合并到一起,则B树就会减少一层。
下图给出了在3阶B树中删除关键码时,出现被删结点中关键码个数发生下溢的情况以及处理示意图。

2.1.2 B+树

1、B+树引入

B+树是B树的变体,是由B树和索引顺序访问方法(ISAM,Indexed Sequential Access Methed)演变而来,B+树是为磁盘或其他直接存取辅助设备设计的一种平衡查找树。在B+树中,所有记录都是按键值的大小顺序存放在同一层的叶子结点上,由各叶子节点指针进行拼接。

一棵m阶的B+树在结构上与m阶的B树相同,但在关键码的内部安排上有所不同。具体如下:
(1)具有m棵子树的结点含有m个关键码,即每一个关键码对应一棵子树;
(2)关键码K,是它所对应的子树的根结点中的最大(或最小)关键码;
(3)所有的终端结点中包含了全部关键码信息,及指向关键码记录的指针;
(4)各终端结点按关键码的大小次序链在一起,形成单链表,并设置头指针 与B_树类似,在B树中,结点内的关键码仍然有序排列,并且对同一结点内的任意 两个关键码K和K,若K,<K,则K,小于K,对应的子树中的所有关键码。
与二叉排序树和2-3树最显著的区别是B+树只在终端结点存储记录,内部结点存储关键码,但是这些关键码只是用于引导查找的。这意味着内部结点在结构上与终端结点有显著的区别。内部结点存储关键码用于引导查找,把每个关键码与一个指向子女结点 的指针相关联;终端结点存储实际记录,在B+树纯粹作为索引的情况下则存储关键码和指向实际记录的指针。一个B+树的终端结点一般链接起来,形成一个链表,这样,通过访问链表中的所有终端结点,就可以按照排序的顺序遍历全部记录。
例如,下图所示为一棵3阶的B+树,通常在B+树上有两个头指针,一个指向根结点,另一个指向关键码最小的终端结点。因此,可以对B+树进行两种查找操作:一种是 从最小关键码起顺序查找,另一种是从根结点开始随机查找。

在B+树上进行随机查找、插入和删除的过程基本上与B树类似。除了查找必须一直到达终端结点外,在一棵B树中的查找几乎与在一棵B树中的查找完全一样。即使在一个内部结点找到了待查找的关键码值,但它只是用来引导索引的,并不提供对实际记录的访问,所以在B+树查找:B+树中查找时,必须到达包含有该关键码值的终端结点。
B+树插入:B+树的插入仅在终端结点上进行,当结点中的关键码个数大于m时要分裂成两个结点,并且它们的双亲结点中应同时包含这两个结点中的最大关键码。
B+树删除:B+树的删除也仅在终端结点上进行,当终端结点中的最大关键码被删除时,其在非终端结点中的值可以作为一个“分界关键码”存在。若因删除而使结点中关键码的个数少于 时,和兄弟结点的合并过程和B树类似。
B+树范围查找:B+树特别适合范围查找。一旦找到了范围中的第一个记录,通过顺序处理结点中的 其余记录,然后继续下去,尽可能地深入终端结点,就可以找到范围中的全部记录。

2、B+树查找

先来看一个B+树,其高度为2,每页可存放4条记录,扇出(fan out)为5,如下图。
所有记录都在叶子节点上,并且都是顺序存放的,如果用户从最左边的叶子节点开始顺序遍历,可以得到所有键值的顺序排序:5、10、15、20、25、30、50、55、60、65、70、75、80、85、90。

2.1.3 小结

B树和B+树统称为B树,是需要插入、删除和关键码范围检索的应用程序的标准组织方法,解决了实现基于磁盘的检索时遇到的下列所有问题:
(1)B树总是树高平衡的,所有叶结点都在同一层;
(2)査找、插入和删除等操作只影响一些结点(即磁盘页),因此性能很好;
(3)B树把相关的记录放在同一个磁盘页中,从而利用了访问局部性原理;
(4)B树保证树中至少有一定比例的结点是满的,这样能够改进空间的利用率,同时 在查找和更新操作期间减少对磁盘的读取次数。

2.2 B+树索引

人们谈论索引的时候,如果没有特别指明类型,那多半说的是B+Tree索引(因为大多数MySQL引擎都支持这种索引),它使用Tree数据结构来存储数据。

存储引擎以不同的方式使用B+Tree索引,性能也各有不同,各有优劣。例如, MyISAM 使用前缀压缩技术使得索引更小,但 InnoDB则按照原数据格式进行存储。再如MyISAM 索引通过数据的物理位置引用被索引的行,而 InnoDB则根据主键引用被索引的行。

B-Tree通常意味着所有的值都是按顺序存储的,并且每一个叶子页到根的距离相同。

B-Tree索引能够加快访问数据的速度,因为存储引擎不再需要进行全表扫描来获取需要的数据,取而代之的是从索引的根节点开始进行搜索,根节点存放了指向子节点的指针,存储引擎根据这些指针向下层查找。通过比较节点页的值和要查找的值可以找到合适的指针进入下层子节点,这些指针实际上定义了的上限和下限。最终存储引擎要么是找到对应的值,要么该记录不存在。

其中,叶子节点比较特别,它们的指针指向的是被索引的数据,而不是其他的节点页(ps:不同引擎的“指针”类型不同),树的深度和表的大小直接相关。

B-Tree对索引列是顺序组织存储的,所以很适合查找范围数据。例如,在一个基于文本域的索引树上,按字母顺序传递连续的值进行查找是非常合适的,所以像“找出所有以I到K开头的名字”这样的查找效率会非常高。

请注意,索引对多个值进行排序的依据是CREATE TABLE语句中定义索引时列的顺序。看一下最后两个条目,两个人的姓和名都一样,则根据他们的出生日期来排列顺序。

可以使用B-Tree索引的查询类型。 B-Tree索引适用于全键值、键值范围或键前缀查找。 其中键前缀查找只适用于根据最左前缀的查找,前面所述的索引对如下类型的查询有效。

全值匹配
全值匹配指的是和索引中的所有列进行匹配,例如前面提到的索引可用干查找姓名为Cuba Allen、出生于1960-01-01的人。

匹配最左前缀
前面提到的索引可用于查找所有姓为Alen的人,即只使用索引的第一列。

匹配列前缀
也可以只匹配某一列的值的开头部分,例如前面提到的索引可用于查找所有以J开头的姓的人,这里也只使用了索引的第一列。

匹配范围值
例如前面提到的索引可用于查找姓在Aen和Barrymore之间的人,这里也只使用了索引的第一列。

精确匹配某一列并范围匹配另外一列
前面提到的索引也可用于查找所有姓为Allen,并且名字是字母K开头(比如Kim、Karl等)的人,即第一列last_name全匹配,第二列first_nane范围匹配。

只访问索引的查询
B-Tree通常可以支持“只访问索引的查询”,即查询只需要访问索引,而无须访问数据行,后面我们将单独讨论这种“覆盖索引”的优化。

因为索引树中的节点是有序的。所以除了按值查找之外,索引还可以用于查询中的ORDER BY操作(按顺序查找),一般来说,如果B-Tree可以按照某种方式查找到值,那么也可以按照这种方式用于排序。所以,如果ORDER BY子句满足前面列出的几种查询类型,则这个索引也可以满足对应的排序需求。

附:一些关于 B-Tree索引的限制
(1)如果不是按照索引的最左列开始查找,则无法使用索引。例如上面例子中的索引无法用于查找名字为Bill的人,也无法查找某个特定生日的人,因为这两列都不是最左数据列。类似地,也无法查找姓氏以某个字母结尾的人;
(2)不能跳过索引中的列,也就是说,前面所述的索引无法用于查找姓为 Smith并且在某个特定日期出生的人,如果不指定名(first_name),则 MySQL只能使用索引的第一列;
(3) 如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查找。例如 有查询
WHERE last name=‘Smith’ AND first_name LIKE ‘J%’ AND dob=1976-12-23,
这个查询只能使用索引的前两列,因为这里LIKE是一个范围条件(但是服务器可以把其余列用于其他目的)。如果范围查询列值的数量有限,那么可以通过使用多个等于条件来代替范围条件。

到这里读者应该可以明白,前面提到的索引列的顺序是多么的重要:这些限制都和索引 列的题序有关

三、哈希索引

哈希索引( hash index)基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码( hash code),哈希码是个较小的值,并且不同键值的行计算出来的哈希码也不一样。哈希索引将所有的哈希码存储在素引中,同时在哈希表中保存指向每个数据行的指针。

在 MySQL中,只有 Memory引擎显式支持哈希索引,这也是 Memory引擎表的默认索引类型, Memory引擎同时也支持 B-Tree索引,值得一提的是, Memory引擎是支持非唯一哈希索引的,这在数据库世界里面是比较与众不同的。如果多个列的哈希值相同,索引会以链表的方式存放多个记录指针到同一个哈希条目中。
下面来看一个例子。假设有如下表

create table testhash (
   fname varchar(50)  NOT NULL,
   lname varchar(50)  NOT NULL,
   key using hash(fname)
)engine = memory;

注意,在MySQL中,只有Memory引擎显示支持哈希索引,也是默认索引类型。所以我们这里将testhash表的engine设置为Memory,就是使用了哈希索引。

在这里插入图片描述
假设索引使用假想的哈希函数f(),它返回下面的值(都是示例数据,非真实数据):

f('Arjen') = 2323
f('Baron') = 7437
f('Peter') = 8784
f('Vadim') = 2458

则哈希索引的数据结构如下:

注意每个槽的编号是顺序的,但是数据行不是。现在,来看如下查询:

MySQL先计算’Peter’的哈希值,并使用该值寻找对应的记录指针,因为f(‘Peter’)= 8784,所以 MySQL在索引中查找8784,可以找到指向第3行的指针,最后一步是比较第三行的值是否为 ‘Peter’,以确保就是要查找的行。

因为索引自身只需存储对应的哈希值,所以索引的结构十分紧凑,这也让哈希索引查找 的速度非常快。然而,哈希索引也有它的限制。

附:哈希散列的限制 ·
(1)哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能的影响并不明显。
(2)哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序。
(3)哈希索引也不支持部分索引列匹配查询,因为哈希索引始终是使用索引列的全部内容来计算哈希值的。例如,在数据列(A,B)上建立哈希索引,如果查询只有数据列A,则无法使用该索引。
(4)哈希索引只支持等值比较查询,包括=、IN()、<=>,也不支持任何范围查询,例如 WHERE price>100。
(5)访问哈希索引的数据非常快,除非有很多哈希冲突(不同的索引列值却有相同的哈希值),当出现哈希冲突的时候,存储引擎必须遍历链表中所有的行指针,逐行进行比较,直到找到所有符合条件的行。
(6)如果哈希冲突很多的话,一些索引维护操作的代价也会很高。例如,如果在某个选择性很低(哈希冲突很多)的列上建立哈希索引,那么当从表中删除一行时,存储引擎需要遍历对应哈希值的链表中的每一行,找到并删除对应行的引用,冲突越多,代价越大。

因为这些限制,哈希索引只适用于某些特定的场合。而一旦适合哈希索引,则它带来的性能提升将非常显著,举个例子,在数据仓库应用中有一种经典的“星型” schema,需要关联很多查找表,哈希索引就非常适合查找表的需求。

自适应哈希索引
InnoDB引擎有一个特殊的功能叫做“自适应哈希索引( adaptive hash index)",当 InnoDB注意到某些索引值被使用得非常频繁时,它会在内存中基于B-Tree索引之上再创建一个哈希索引,这样就让B-Tree索引也具有哈希索引的一些优点,比如快速的哈希查找。这是一个完全自动的,内部的行为,用户无法控制或者配置,不过如果有必要,完全可以关闭该功能。

创建自定义哈希索引:如果存储引擎不支持哈希索引,则可以模拟像InnoDB一样创建哈希索引,这可以享受一些哈希索引的便利,例如只需要很小的索引就可以为超长的键创建索引。

思路很简单:在B-Tree基础上创建一个伪哈希索引,这和真正的哈希索引不是一回事, 因为还是使用B-Tree进行查找,但是它使用哈希值而不是键本身进行索引查找,你需要做的就是在查询的WHERE子句中手动指定使用哈希函数。

下面是一个实例,例如需要存储大量的URL,井需要根据URL进行搜索查找,如果使用B-Tree来存储URL,存储的内容就会很大,因为URL本身都很长,正常情况下会有如下查询:

mysql> select id FROM url WHERE url="http://www.baidu.com";

若删除原来URL列上的索引,而新增一个被索引的url_crc列,使用CRC32做哈希,就可以使用下面的方式查询:

mysql> select id FROM url WHERE url="http://www.baidu.com" AND url_crc=CRC32("http://www.baidu.com");

这样做的性能会非常高,因为 MySQL优化器会使用这个选择性很高而体积很小的基于
url_crc列的索引来完成查找(在上面的案例中,索引值为xxxxxx)。即使有多个记录有相同的索引值,查找仍然很快,只需要根据哈希值做快速的整数比数就能找到索引条目,然后一一比较返回对应的行。另外一种方式就是对完整的URL字符串做索引,那样会比较慢。

这样实现的缺陷是需要维护哈希值。可以手动维护,也可以使用触发器实现,下面的案例演示了触发器如何在插入和更新时维护url_crc列,首先创建如下表:

create table pseudohash (
id int unsigned not null auto_increment,
url varchar(255) not null,
url_crc int unsigned not null default 0,
primary key(id)
);

然后创建触发器,先临时修改一下语句分隔符,这样就可以在触发器定义中使用分号

create trigger pseudohash_crc_ins before insert on pseudohash 
for each row  begin  set  new.url_crc=crc32(new.url);
end;
create trigger pseudohash_crc_upd before update on pseudohash 
for each row begin set new.url_crc=crc32(new.url);
end;

剩下的工作就是验证一下触发器如何维护哈希索引

insert into pseudohash (url)  values("http://www.mysql.com");
select * from pseudohash;
update pseudohash set url = 'http://www.mysql.com/' where id = 1;
select * from pseudohash;

如果采用这种方式,记住不要使用SHA1()和MD5()作为哈希函数,因为这两个函数计算出来的哈希值是非常长的字符串,会浪费大量空间,比较时也会更慢。SHA1()和MD5()是强加密函数,设计目标是最大限度消除冲突,但这里并不需要这样高的要求。简单哈希函数的冲突在一个可以接受的范围,同时又能够提供更好的性能。

哈希冲突

如果数据表非常大,CRC32()会出现大量的哈希冲突,则可以考虑自己实现一个简单的64位哈希函数,这个自定义函数要返回整数,而不是字符串。一个简单的办法可以使用MD5()函数返回值的一部分来作为自定义哈希函数。这可能比自己写一个哈希算法的性能要差,不过这样实现最简单:

select conv (right(md5('http://www.mysql.com/'),16),16,10) as HASH64;

处理哈希冲突,当使用哈希索引进行查询的时候,必须在WHERE子句中包含常量值:

select id from url where url_crc=CRC32("http://www.mysql.com") and url="http://www.mysql.com";

一旦出现哈希冲突,另一个字符串的哈希值也恰好是1560514994,则下面的查询是无法正确工作的。

select id from url where url_crc=CRC32("http://www.mysql.com");

因为会出现"生日悖论",出现哈看冲突的概率的增长速度可能比想象的要快得多.CRC32()返回的是32位的整数,当索引有93000条记录时出现冲突的概率是1%,例如我们将/usr/share/dict/words中的词导入数据表并进行CRC32()计算,最后会有98569行。这就已经出现一次哈希冲突了,冲突让下面的查询返国了多条记录:

select word,crc from words where crc=CRC32('gnu');

正确的写法应该如下:

select word,crc from words where crc=CRC32('gnu') and word='gnu';

要避免冲突问题,必须在WHERE条件中带入哈希值和对应列值。如果不是想查询具体值,例如只是统计记录数(不精确的),则可以不带入列值,直接使用CRC32()的哈希值查询即可。还可以使用如FMV64()函数作为哈希函数,这是移植自Percona Server的函数,可以以插件的方式在任何MySQL版本中使用,哈希值为64位,速度快,且冲突比CRC32()要少很多。

四、全文索引

全文索引是一种特殊类型的索引,它查找的是文本中的关键词,而不是直接比较索引中的值。

全文搜索和其他几类索引的匹配方式完全不一样。它有许多需要注意的细节,如停用词、词干和复数、布尔搜索等,全文索引更类似于搜索引擎做的事情,而不是简单的WHERE条件匹配。

在相同的列上同时创建全文索引和基于值的B-Tree索引不会有冲突,全文索引适用于 MATCH AGAINST操作,而不是普通的WHERE条件操作。

五、小结

本文介绍MySQL索引机制,属于MySQL索引内容入门级博客,全文分为四个部分:“索引引入及其优点”、“B+树索引”、“哈希索引”、“全文索引”,一步步由浅入深介绍Mysql的三种索引,其中B+树索引最重要,希望对MySQL初学者有用。

天天打码,天天进步!

发布了190 篇原创文章 · 获赞 71 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/qq_36963950/article/details/104644043