您的位置:首页 > 八字

数据库索引是什么意思(索引是什么意思)

数据库索引是什么意思(索引是什么意思)

别再一知半解啦,索引其实就这么回事

作者 | Amazing10

责编 | 屠敏

头图 | CSDN 下载自视觉中国

本文为「业余码农」投稿

索引的概念基本所有人都会遇到过,就算没有了解过数据库中的索引,在生活中也不可避免的接触到。比方说书籍的目录,字典的查询页,图书馆的科目检索等等。其实这些都是一种索引,并且所起到的作用大同小异。

而对于数据库而言,只不过是将索引的概念抽象出来,让建立索引的过程更为灵活而自由,从而可以在不同的场景下优化数据库的查询效率。

索引在数据库的实际应用场景中十分普遍,数据库的优化也离不开对索引的优化。同时,索引相关的知识也是面试高频的考点之一,是应试者理论结合现实最为直接的体现。

因此,本文将从基础理论出发,介绍 MySQL 按照逻辑角度的索引分类和实现,通过数据结构的实现原理阐述不同结构对建立索引带来的优劣势,同时针对物理存储的方式对索引的组织特点和应用场景进行分析。最后根据不同的应用场景尽可能的探究如何建立起高性能的索引。文章结构如下:

概念

什么是索引?

索引似乎并没有十分明确的定义,更多的是一种定性的描述。简单来讲,索引就是一种将数据库中的记录按照特殊形式存储的数据结构。通过索引,能够显著地提高数据查询的效率,从而提升服务器的性能。

专业一点来说呢,索引是一个排好序的列表,在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址。在数据库十分庞大的时候,索引可以大大加快查询的速度,这是因为使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数据对应的物理地址然后访问相应的数据。

说起索引,其实并不是 MySQL 数据库特有的机制,在关系型数据库中都会有类似不同的实现。这里我们也只是讨论 MySQL 数据库中的索引实现。

事实上,说是 MySQL 的索引其实并不准确。因为在 MySQL 中,索引是在存储引擎层而不是服务器层实现的。这意味着我们所讨论的索引准确来说是 InnoDB 引擎或 MyISAM 引擎或其它存储引擎所实现的。

所以索引即便是在 MySQL 中也没有统一的标准,不同存储引擎的所实现的索引工作方式也并不一样。不是所有的存储引擎都支持相同类型的索引,即便是多个引擎支持同一种类型的索引,其底层的实现也可能不同。

为什么需要索引

说了这么多,索引似乎就是给数据库添加了一个「目录页」,能够方便查询数据。但是索引的作用就仅此而已了吗,为什么需要大费周章的建立并优化索引?

说个题外话,我其实查字典从来都不喜欢查目录页,无论是查中文还是英文。因为觉得那样很慢,一个个找索引,效率很低。我习惯用的方式就是直接翻开字典,根据翻开的位置进行前后调整。比方说我想找「酱 JIANG」字,会先随机翻到一页,可能是「F」开头,在「J」前面,就往后翻一点;如果随机翻到「L」,那就往前翻一点。重复直至找到。

这大概就是类似于二分查找的方式,看起来好像是摆脱了索引的束缚,并且也能够获得比较高的查询效率。但是其实转念一想,在计算机的运行处理中,「一个个找索引」这个过程其实非常快,不能跟我们手动比对偏旁部首的效率相提并论。同时,为什么我可以直接翻开字典根据字母进行调整呢,这其实不就是因为我的脑子里存在一个大概的「索引表」,知道每个字母大概对应于字典的哪一个位置。虽然是模糊的,但却是真实存在的。(好不容易强行解释了一波...)

如此一来,可以看出索引的一大好处是如其概念中所提及的,使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数据对应的物理地址然后访问相应的数据。这样的方式自然减少了服务器在响应时所需要对数据库扫描的数据量。

不仅如此,在执行数据库的范围查询时,若不使用索引,那么MySQL会先扫描数据库的所有行数据并从中筛选出目标范围内的行记录,将这些行记录进行排序并生成一张临时表,然后通过临时表返回用户查询的目标行记录。这个过程会涉及到临时表的建立和行记录的排序,当目标行记录较多的时候,会大大影响范围查询的效率。

所以当添加索引时,由于索引本身具有的顺序性,使得在进行范围查询时,所筛选出的行记录已经排好序,从而避免了再次排序和需要建立临时表的问题。

同时,由于索引底层实现的有序性,使得在进行数据查询时,能够避免在磁盘不同扇区的随机寻址。使用索引后能够通过磁盘预读使得在磁盘上对数据的访问大致呈顺序的寻址。这本质上是依据局部性原理所实现的。

局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。程序运行期间所需要的数据通常比较集中。由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间) ,因此对于具有局部性的程序来说,磁盘预读可以提高I/O效率。

磁盘预读要求每次都会预读的长度一般为页的整数倍。而且数据库系统将一个节点的大小设为等于一个页,这样每个节点只需要一次 I/O 就可以完全载入。这里的页是通过页式的内存管理所实现的,概念在这里简单提一嘴。

分页机制就是把内存地址空间分为若干个很小的固定大小的页,每一页的大小由内存决定。这样做是为了从虚拟地址映射到物理地址,提高内存和磁盘的利用率。

所以呢,总结一下。索引的存在具有很大的优势,主要表现为以下三点:

索引大大减少了服务器需要扫描的数据量

索引可以帮助服务器避免排序和临时表

索引可以将随机 I/O 变成顺序 I/O

以上三点能够大大提高数据库查询的效率,优化服务器的性能。因此一般来说,为数据库添加高效的索引对数据库进行优化的重要工作之一。

不过,凡事都有两面性。索引的存在能够带来性能的提升,自然在其它方面也会付出额外的代价。

索引本身以表的形式存储,因此会占用额外的存储空间;

索引表的创建和维护需要时间成本,这个成本随着数据量增大而增大;

构建索引会降低数据的修改操作(删除,添加,修改)的效率,因为在修改数据表的同时还需要修改索引表;

所以对于非常小的表而言,使用索引的代价会大于直接进行全表扫描,这时候就并不一定非得使用索引了。没办法,成年人的世界总是这么的趋利避害。

逻辑分类

从逻辑的角度来对索引进行划分的话,可以分为单列索引、全文索引、组合索引和空间索引。其中单列索引又可分为主键索引、唯一索引和普通索引。这里的逻辑可以理解为从 SQL 语句的角度,或者是从数据库关系表的角度。下面就简单介绍这些索引的作用和用法,以及在修改表的时候如何添加索引。

主键索引

即主索引,根据主键建立索引,不允许重复,不允许空值;

主键:数据库表中一列或列组合(字段)的值,可唯一标识表中的每一行。

加速查询 + 列值唯一(不可以有)+ 表中只有一个

ALTER TABLE 'table_name' ADD PRIMARY KEY pk_index('col');

唯一索引

用来建立索引的列的值必须是唯一的,允许空值。唯一索引不允许表中任何两行具有相同的索引值。比方说,在 employee 表中职员的姓 name 上创建了唯一索引,那么就表示任何两个员工都不能同姓。

加速查询 + 列值唯一(可以有)

ALTER TABLE 'table_name' ADD UNIQUE index_name('col');

普通索引

用表中的普通列构建的索引,没有任何限制。

仅加速查询

ALTER TABLE 'table_name' ADD INDEX index_name('col');

全文索引

用大文本对象的列构建的索引

ALTER TABLE 'table_name' ADD FULLTEXT INDEX ft_index('col');

组合索引

用多个列组合构建的索引,这多个列中的值不允许有空值。

多列值组成一个索引,专门用于组合搜索,其效率大于索引合并。

ALTER TABLE 'table_name' ADD INDEX index_name('col1','col2','col3');

在对多列组合建立索引时,会遵循「最左前缀」原则。

最左前缀原则:顾名思义,就是最左优先,上例中我们创建了 (col1, col2, col3) 多列索引,相当于创建了 (col1) 单列索引,(col1, col2) 组合索引以及 (col1, col2, col3) 组合索引。

所以当我们在创建多列索引时,要根据业务场景,将 where 子句中使用最频繁的一列放在最左边。

空间索引

对空间数据类型的字段建立的索引,底层可通过 R 树实现。只不过使用较少,了解即可。

实现原理

我们知道,索引的底层本身就是通过数据结构来进行实现的。那么根据其底层的结构,常见的索引类型可分为哈希索引,BTree 索引,B+Tree 索引等。这里我们就主要来介绍这三种索引背后的实现机制。

哈希索引

顾名思义,哈希索引是通过哈希表实现的。哈希表的特点在之前的文章「九大数据结构」中已经详细介绍过。通过哈希表的键值之间的对应关系,能够在查询时精确匹配索引的所有列。哈希索引将所有的根据索引列计算出来的哈希码存储在索引中,同时将指向每个数据行的指针保存在哈希表中。

上图是通过哈希索引查询行数据的示意图,可以发现哈希索引同样会发生哈希冲突,并且是通过链地址法解决冲突的。当发送冲突时,还需要对链表进行遍历对比,才能够找到最终的结果。

在 MySQL 中,只有 Memory 存储引擎显式的支持哈希索引,而innodb是隐式支持哈希索引的。

这里的隐式支持是指,innodb引擎有一个特殊的功能 “自适应哈希索引”,当innodb注意到一些索引值被使用的非常频繁时,且符合哈希特点(如每次查询的列都一样),它会在内存中基于 B-Tree 索引之上再创建一个哈希索引。这样就让 BTree 索引也具有哈希索引的一些有点。这是一个完全自动的、内部的行为。

由于哈希结构的特殊性,其用于非常高的检索效率,通过哈希函数的映射可以一步到位。但是同样也是因为结构的特殊,导致哈希索引只适用于某些特定的场合。哈希索引的限制[1]:

不支持范围查询,比如 WHERE a > 5;只支持等值比较查询,包括=、IN 、<=>

无法被用来避免数据的排序操作;因为经过了哈希函数的映射过程,使得丢失了哈希前后的大小关系,从而无法按照索引值的顺序存储。

不支持部分索引列的匹配查找,因为哈希索引始终使用索引列的全部内容来计算哈希值。例如在数据列 (A, B) 上建立哈希索引,如果查询只有数据列 A,则无法使用该索引。

无法避免表扫描。因为当出现哈希冲突的时候,存储引擎必须遍历链表(拉链法)中所有的行指针,逐行进行比较,直到找到所有符合条件的行。

哈希冲突很多的情况下,其索引维护的代价很高,并且性能并不一定会比 BTree 索引高。

BTree 索引

BTree 实际上是一颗多叉平衡搜索树。从名字可以看出,BTree 首先是一颗多叉搜索树,这意味着它是具有顺序的;其次 BTree 还是平衡的,这意味着它的左右子树高度差小于等于1。

事实上一颗 BTree 需要满足以下几个条件:

每个叶子结点的高度都是一样的;

每个非叶子结点由 n-1 个 key 和 n 个指针 point 组成,其中 d<=n<=2d, key 和 point 相互间隔,结点两端一定是 key;

叶子结点指针都为 ;

非叶子结点的key都是 [key, data] 二元组,其中 key 表示作为索引的键,data 为键值所在行的数据;

一颗常见的BTree树见下图。

这是一颗三阶的BTree,可通过键值的大小排序进行数据的查询和检索,其中叶子节点的指针都为空,因此省略没画。从上图可以发现,BTree 的树形状相较于我们之前常见的二叉树等结构,更为扁平和矮胖。

之所以这样设计,还是跟磁盘读取的特点有关。我们知道在建立索引时,也是需要占据物理空间的。而实际上当数据量比较大的时候,索引文件的大小也十分吓人。考虑到一个表上可能有多个索引、组合索引、数据行占用更小等情况,索引文件的大小可能达到物理盘中数据的1/10,甚至可达到1/3。

这就意味着索引无法全部装入内存之中。当通过索引对数据进行访问时,不可避免的需要对磁盘进行读写访问。同时我们还知道,内存的读写速度是磁盘的几个数量级。因此在对索引结构进行设计时要尽可能的减少对磁盘的读写次数,也就是所谓的磁盘 I/O 次数。

这也就是索引会采用 BTree 这种扁平树结构的原因,树的层数越少,磁盘I/O的次数自然就越少。不仅如此,我们上面提到过磁盘预读的局部性原理。根据这个原理再加上页表机制,能够在进行磁盘读取的时候更大化的提升性能。

BTree 相较于其它的二叉树结构,对磁盘的 I/O 次数已经非常少了。但是在实际的数据库应用中仍有些问题无法解决。

一是无法定位到数据行。通过 BTree 可以根据主键的排序定位出主键的位置,但是由于数据表的记录有多个字段,仅仅定位到主键是不够,还需要定位到数据行。虽然这个问题可以通过在 BTree 的节点中存储数据行或者增加定位的字段,但是这种方式会使得 BTree 的深度大幅度提高,从而也导致 I/O 次数的提高。

二是无法处理范围查询。在实际的应用中,数据库范围查询的频率非常高,而 BTree 只能定位到一个索引位置。虽然可以通过先后查询范围的左右界获得,但是这样的操作实际上无法很好的利用磁盘预读的局部性原理,先后查询可能会造成通过预读取的物理地址离散,使得 I/O 的效率并不高。

三是当数据量一大时,BTree的高度依旧会变得很高,搜索效率还是会大幅度的下降。

问题总是推动改进的前提。基于以上的问题考虑,就出现了对 BTree 的优化版本,也就是 B+Tree。

B+Tree索引

B+Tree 一看就是在 BTree 的基础上做了改进,那么到底改变了什么呢。废话不多说,先上图。

上图实际上是一种带有顺序索引的 B+Tree,与最基本的 B+Tree 的区别就在于叶子节点是否通过指针相连。一般数据库中常用的结构都是这种带有顺序索引的 B+Tree。后文探讨的也都是带顺序索引的 B+Tree 结构。

对比 BTree 和 B+Tree,我们可以发现二者主要在以下三个方面上的不同:

非叶子节点只存储键值信息,不再存储数据。

所有叶子节点之间都有一个链指针,指向下一个叶子节点。

数据都存放在叶子节点中。

看着 B+Tree,像不像是一颗树与一个有序链表的结合体。因而其实 B+Tree 也就是带有树和链表的部分优势。树结构使得有序检索更为简单,I/O 次数更少;有序链表结构使得可以按照键值排序的次序遍历全部记录。

B+Tree 在作为索引结构时能够带来的好处有:

一,I/O 次数更少。这是因为上文也说过,BTree 的节点是存放在内存页中的。那么在相同的内存页大小(一般为4k)的情况下,B+Tree 能够存储更多的键值,那么整体树结构的高度就会更小,需要的 I/O 次数也就越小。

二,数据遍历更为方便。这个优势很明显是由有序链表带来的。通过叶子节点的链接,使得对所有数据的遍历只需要在线性的链表上完成,这就非常适合区间检索和范围查询。

三,查询性能更稳定。这自然是由于只在叶子节点存储数据,所以所有数据的查询都会到达叶子节点,同时叶子节点的高度都相同,因此理论上来说所有数据的查询速度都是一致的。

正是由于 B+Tree 优秀的结构特性,使得常用作索引的实现结构。在 MySQL 中,存储引擎 MyISAM 和 InnoDB 都分别以 B+Tree 实现了响应的索引设计。

物理存储

虽说 B+Tree 结构都可以用在 MyISAM 和 InnoDB,但是这二者对索引的在物理存储层次的实现方式却不相同。InnoDB 实现的是聚簇索引,而 MyISAM 实现的却是非聚簇索引。在介绍聚簇索引之前,我们需要先了解以下啥是佩奇,不对,是啥是「主键索引」和「辅助索引」。

其实概念很简单。我们刚刚不是在讲 B+Tree 的时候说过,树的非叶子节点只存储键值。没错就是这个键值,当这个键值是数据表的主键时,那么所建立的就是主键索引;当这个键值是其它字段的时候,就是辅助索引。因而可以得出,主键索引只能有一个,而辅助索引却可以有很多个。

聚簇索引和非聚簇索引的区别也就是根据其对应的主键索引和辅助索引的不同特点而实现的。

聚簇索引

说回聚簇索引。先丢个定义。

聚簇索引的主键索引的叶子结点存储的是键值对应的数据本身;辅助索引的叶子结点存储的是键值对应的数据的主键键值。

这句话的信息量挺大的。首先,分析第一句话,主键索引的叶子节点存储的是键值对应的数据本身。

我们知道,主键索引存储的键值就是主键。那么也就是说,聚簇索引的主键索引,在叶子节点中存储的是主键和主键对应的数据。数据和主键索引是存储在一起的,一起作为叶子节点的一部分。

然后,分析第二句话,辅助索引的叶子结点存储的是键值对应的数据的主键键值。

我们又知道,辅助索引存储的键值是非主键的字段。那就也就是说,通过辅助索引,可以找到非主键字段对应的数据行中的主键。

重点来了。当然主键索引和辅助索引一结合,能干啥呢。当直接采用主键进行检索时,可通过主键索引直接获得数据;而当采用非主键进行检索时,先需要通过辅助索引来获得主键,然后再通过这个主键在主键索引中找到对应的数据行。

举个例子吧。假设有这么一个数据表。

那么采用聚簇索引的存储方式,对应的主键索引为:(主键为ID)

对应的辅助索引为:(键值为Name,大概的意思):

所以当使用where ID = 7这样的条件查找主键,则按照B+树的检索算法即可查找到对应的叶节点,之后获得行数据。对Name列进行条件搜索,则需要两个步骤:第一步在辅助索引B+树中检索Name,到达其叶子节点获取对应的主键。第二步使用主键在主键索引B+树种再执行一次B+树检索操作,最终到达叶子节点即可获取整行数据。

最后把以上过程整理总结一下,聚簇索引实际上的过程就分为以下两个过程。现在这个图应该能够看懂了吧。

非聚簇索引

学完了聚簇索引,非聚簇索引就简单多啦。同样,先上定义。

非聚簇索引的主键索引和辅助索引几乎是一样的,只是主索引不允许重复,不允许空值,他们的叶子结点都存储指向键值对应的数据的物理地址。

与聚簇索引来对比着看,上面的定义能够说明什么呢。首先,主键索引和辅助索引的叶子结点都存储着键值对应的数据的物理地址,这说明无论是主键索引还是辅助索引都能够通过直接获得数据,而不需要像聚簇索引那样在检索辅助索引时还得多绕一圈。

同时还说明一个点,叶子结点存储的是物理地址,那么表示数据实际上是存在另一个地方的,并不是存储在B+树的结点中。这说明非聚簇索引的数据表和索引表是分开存储的。

同样,对非聚簇索引的检索过程来个总结。

无论是主键索引还是辅助索引的检索过程,都只需要通过相应的 B+Tree 进行搜索即可获得数据对应的物理地址,然后经过依次磁盘 I/O 就可访问数据。

对比聚簇索引和非聚簇索引,可以发现二者最主要的区别就是在于是否在 B+Tree 的节点中存储数据,也就是数据和索引是否存储在一起。这个区别导致最大的问题就是聚簇索引的索引的顺序和数据本身的顺序是相同的,而非聚簇索引的顺序跟数据的顺序没有啥关系。

索引优化

介绍了这么多的索引,其实最终都是为了建立高性能的索引策略,对数据库中的索引进行优化。索引的优化有很多角度,针对特定的业务场景可采用不同的优化策略。这里考虑到文章篇幅,就不具体介绍,下次再出一篇专门讲索引优化的文章。简单列举一下在进行优化时可以考虑的几个方向:

1 独立的列。索引列不能是表达式的一部分,也不能是函数的参数。

2 前缀索引和索引选择性。这二者实际上是相互制约的关系,制约条件在于前缀的长度。一般应选择足够长的前缀以保证较高的选择性(保证查询效率),同时又不能太长以便节省空间。

3 尽量使用覆盖索引。覆盖索引是指一个索引包含所有需要查询的字段的值,这样在查询时只需要扫描索引而无须再去读取数据行,从而极大的提高性能。

4 使用索引扫描来做排序。要知道,扫描索引本身是很快的,在设计索引时,可尽可能的使用同一个索引既满足排序,又满足查找行数据。

最后,在建立索引时给几个小贴士:

1 优先使用自增key作为主键

2 记住最左前缀匹配原则

3 索引列不能参与计算

4 选择区分度高的列作索引

5 能扩展就不要新建索引

总结

索引的概念和原理是我们在了解和精通数据库过程中无法逃避的重点,而事实上建立高性能的索引对实际的应用场景也具有重要意义。本文的目的依旧是由浅入深的介绍索引这么个东西,从概念到实现再到最终的优化策略。

点到为止,学无止境。掌握了基本的理论和概念后,还需要在实际的服务器开发场景中针对具体的问题和服务进行索引优化方案的设计和使用。功力不够,仍需努力。

Reference

高性能 MySQL,Baron Schwartz 等人著,电子工业出版社

公众号码海系列文章:

https://www.jianshu.com/p/9e9aca844c13

https://www.runoob.com/mysql/mysql-index.html

https://www.cnblogs.com/Aiapple/p/5693239.html

https://blog.csdn.net/tongdanping/article/details/79878302

https://www.cnblogs.com/igoodful/p/9361500.html

别再一知半解啦,索引其实就这么回事

作者 | Amazing10

责编 | 屠敏

头图 | CSDN 下载自视觉中国

本文为「业余码农」投稿

索引的概念基本所有人都会遇到过,就算没有了解过数据库中的索引,在生活中也不可避免的接触到。比方说书籍的目录,字典的查询页,图书馆的科目检索等等。其实这些都是一种索引,并且所起到的作用大同小异。

而对于数据库而言,只不过是将索引的概念抽象出来,让建立索引的过程更为灵活而自由,从而可以在不同的场景下优化数据库的查询效率。

索引在数据库的实际应用场景中十分普遍,数据库的优化也离不开对索引的优化。同时,索引相关的知识也是面试高频的考点之一,是应试者理论结合现实最为直接的体现。

因此,本文将从基础理论出发,介绍 MySQL 按照逻辑角度的索引分类和实现,通过数据结构的实现原理阐述不同结构对建立索引带来的优劣势,同时针对物理存储的方式对索引的组织特点和应用场景进行分析。最后根据不同的应用场景尽可能的探究如何建立起高性能的索引。文章结构如下:

概念

什么是索引?

索引似乎并没有十分明确的定义,更多的是一种定性的描述。简单来讲,索引就是一种将数据库中的记录按照特殊形式存储的数据结构。通过索引,能够显著地提高数据查询的效率,从而提升服务器的性能。

专业一点来说呢,索引是一个排好序的列表,在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址。在数据库十分庞大的时候,索引可以大大加快查询的速度,这是因为使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数据对应的物理地址然后访问相应的数据。

说起索引,其实并不是 MySQL 数据库特有的机制,在关系型数据库中都会有类似不同的实现。这里我们也只是讨论 MySQL 数据库中的索引实现。

事实上,说是 MySQL 的索引其实并不准确。因为在 MySQL 中,索引是在存储引擎层而不是服务器层实现的。这意味着我们所讨论的索引准确来说是 InnoDB 引擎或 MyISAM 引擎或其它存储引擎所实现的。

所以索引即便是在 MySQL 中也没有统一的标准,不同存储引擎的所实现的索引工作方式也并不一样。不是所有的存储引擎都支持相同类型的索引,即便是多个引擎支持同一种类型的索引,其底层的实现也可能不同。

为什么需要索引

说了这么多,索引似乎就是给数据库添加了一个「目录页」,能够方便查询数据。但是索引的作用就仅此而已了吗,为什么需要大费周章的建立并优化索引?

说个题外话,我其实查字典从来都不喜欢查目录页,无论是查中文还是英文。因为觉得那样很慢,一个个找索引,效率很低。我习惯用的方式就是直接翻开字典,根据翻开的位置进行前后调整。比方说我想找「酱 JIANG」字,会先随机翻到一页,可能是「F」开头,在「J」前面,就往后翻一点;如果随机翻到「L」,那就往前翻一点。重复直至找到。

这大概就是类似于二分查找的方式,看起来好像是摆脱了索引的束缚,并且也能够获得比较高的查询效率。但是其实转念一想,在计算机的运行处理中,「一个个找索引」这个过程其实非常快,不能跟我们手动比对偏旁部首的效率相提并论。同时,为什么我可以直接翻开字典根据字母进行调整呢,这其实不就是因为我的脑子里存在一个大概的「索引表」,知道每个字母大概对应于字典的哪一个位置。虽然是模糊的,但却是真实存在的。(好不容易强行解释了一波...)

如此一来,可以看出索引的一大好处是如其概念中所提及的,使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数据对应的物理地址然后访问相应的数据。这样的方式自然减少了服务器在响应时所需要对数据库扫描的数据量。

不仅如此,在执行数据库的范围查询时,若不使用索引,那么MySQL会先扫描数据库的所有行数据并从中筛选出目标范围内的行记录,将这些行记录进行排序并生成一张临时表,然后通过临时表返回用户查询的目标行记录。这个过程会涉及到临时表的建立和行记录的排序,当目标行记录较多的时候,会大大影响范围查询的效率。

所以当添加索引时,由于索引本身具有的顺序性,使得在进行范围查询时,所筛选出的行记录已经排好序,从而避免了再次排序和需要建立临时表的问题。

同时,由于索引底层实现的有序性,使得在进行数据查询时,能够避免在磁盘不同扇区的随机寻址。使用索引后能够通过磁盘预读使得在磁盘上对数据的访问大致呈顺序的寻址。这本质上是依据局部性原理所实现的。

局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。程序运行期间所需要的数据通常比较集中。由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间) ,因此对于具有局部性的程序来说,磁盘预读可以提高I/O效率。

磁盘预读要求每次都会预读的长度一般为页的整数倍。而且数据库系统将一个节点的大小设为等于一个页,这样每个节点只需要一次 I/O 就可以完全载入。这里的页是通过页式的内存管理所实现的,概念在这里简单提一嘴。

分页机制就是把内存地址空间分为若干个很小的固定大小的页,每一页的大小由内存决定。这样做是为了从虚拟地址映射到物理地址,提高内存和磁盘的利用率。

所以呢,总结一下。索引的存在具有很大的优势,主要表现为以下三点:

索引大大减少了服务器需要扫描的数据量

索引可以帮助服务器避免排序和临时表

索引可以将随机 I/O 变成顺序 I/O

以上三点能够大大提高数据库查询的效率,优化服务器的性能。因此一般来说,为数据库添加高效的索引对数据库进行优化的重要工作之一。

不过,凡事都有两面性。索引的存在能够带来性能的提升,自然在其它方面也会付出额外的代价。

索引本身以表的形式存储,因此会占用额外的存储空间;

索引表的创建和维护需要时间成本,这个成本随着数据量增大而增大;

构建索引会降低数据的修改操作(删除,添加,修改)的效率,因为在修改数据表的同时还需要修改索引表;

所以对于非常小的表而言,使用索引的代价会大于直接进行全表扫描,这时候就并不一定非得使用索引了。没办法,成年人的世界总是这么的趋利避害。

逻辑分类

从逻辑的角度来对索引进行划分的话,可以分为单列索引、全文索引、组合索引和空间索引。其中单列索引又可分为主键索引、唯一索引和普通索引。这里的逻辑可以理解为从 SQL 语句的角度,或者是从数据库关系表的角度。下面就简单介绍这些索引的作用和用法,以及在修改表的时候如何添加索引。

主键索引

即主索引,根据主键建立索引,不允许重复,不允许空值;

主键:数据库表中一列或列组合(字段)的值,可唯一标识表中的每一行。

加速查询 + 列值唯一(不可以有)+ 表中只有一个

ALTER TABLE 'table_name' ADD PRIMARY KEY pk_index('col');

唯一索引

用来建立索引的列的值必须是唯一的,允许空值。唯一索引不允许表中任何两行具有相同的索引值。比方说,在 employee 表中职员的姓 name 上创建了唯一索引,那么就表示任何两个员工都不能同姓。

加速查询 + 列值唯一(可以有)

ALTER TABLE 'table_name' ADD UNIQUE index_name('col');

普通索引

用表中的普通列构建的索引,没有任何限制。

仅加速查询

ALTER TABLE 'table_name' ADD INDEX index_name('col');

全文索引

用大文本对象的列构建的索引

ALTER TABLE 'table_name' ADD FULLTEXT INDEX ft_index('col');

组合索引

用多个列组合构建的索引,这多个列中的值不允许有空值。

多列值组成一个索引,专门用于组合搜索,其效率大于索引合并。

ALTER TABLE 'table_name' ADD INDEX index_name('col1','col2','col3');

在对多列组合建立索引时,会遵循「最左前缀」原则。

最左前缀原则:顾名思义,就是最左优先,上例中我们创建了 (col1, col2, col3) 多列索引,相当于创建了 (col1) 单列索引,(col1, col2) 组合索引以及 (col1, col2, col3) 组合索引。

所以当我们在创建多列索引时,要根据业务场景,将 where 子句中使用最频繁的一列放在最左边。

空间索引

对空间数据类型的字段建立的索引,底层可通过 R 树实现。只不过使用较少,了解即可。

实现原理

我们知道,索引的底层本身就是通过数据结构来进行实现的。那么根据其底层的结构,常见的索引类型可分为哈希索引,BTree 索引,B+Tree 索引等。这里我们就主要来介绍这三种索引背后的实现机制。

哈希索引

顾名思义,哈希索引是通过哈希表实现的。哈希表的特点在之前的文章「九大数据结构」中已经详细介绍过。通过哈希表的键值之间的对应关系,能够在查询时精确匹配索引的所有列。哈希索引将所有的根据索引列计算出来的哈希码存储在索引中,同时将指向每个数据行的指针保存在哈希表中。

上图是通过哈希索引查询行数据的示意图,可以发现哈希索引同样会发生哈希冲突,并且是通过链地址法解决冲突的。当发送冲突时,还需要对链表进行遍历对比,才能够找到最终的结果。

在 MySQL 中,只有 Memory 存储引擎显式的支持哈希索引,而innodb是隐式支持哈希索引的。

这里的隐式支持是指,innodb引擎有一个特殊的功能 “自适应哈希索引”,当innodb注意到一些索引值被使用的非常频繁时,且符合哈希特点(如每次查询的列都一样),它会在内存中基于 B-Tree 索引之上再创建一个哈希索引。这样就让 BTree 索引也具有哈希索引的一些有点。这是一个完全自动的、内部的行为。

由于哈希结构的特殊性,其用于非常高的检索效率,通过哈希函数的映射可以一步到位。但是同样也是因为结构的特殊,导致哈希索引只适用于某些特定的场合。哈希索引的限制[1]:

不支持范围查询,比如 WHERE a > 5;只支持等值比较查询,包括=、IN 、<=>

无法被用来避免数据的排序操作;因为经过了哈希函数的映射过程,使得丢失了哈希前后的大小关系,从而无法按照索引值的顺序存储。

不支持部分索引列的匹配查找,因为哈希索引始终使用索引列的全部内容来计算哈希值。例如在数据列 (A, B) 上建立哈希索引,如果查询只有数据列 A,则无法使用该索引。

无法避免表扫描。因为当出现哈希冲突的时候,存储引擎必须遍历链表(拉链法)中所有的行指针,逐行进行比较,直到找到所有符合条件的行。

哈希冲突很多的情况下,其索引维护的代价很高,并且性能并不一定会比 BTree 索引高。

BTree 索引

BTree 实际上是一颗多叉平衡搜索树。从名字可以看出,BTree 首先是一颗多叉搜索树,这意味着它是具有顺序的;其次 BTree 还是平衡的,这意味着它的左右子树高度差小于等于1。

事实上一颗 BTree 需要满足以下几个条件:

每个叶子结点的高度都是一样的;

每个非叶子结点由 n-1 个 key 和 n 个指针 point 组成,其中 d<=n<=2d, key 和 point 相互间隔,结点两端一定是 key;

叶子结点指针都为 ;

非叶子结点的key都是 [key, data] 二元组,其中 key 表示作为索引的键,data 为键值所在行的数据;

一颗常见的BTree树见下图。

这是一颗三阶的BTree,可通过键值的大小排序进行数据的查询和检索,其中叶子节点的指针都为空,因此省略没画。从上图可以发现,BTree 的树形状相较于我们之前常见的二叉树等结构,更为扁平和矮胖。

之所以这样设计,还是跟磁盘读取的特点有关。我们知道在建立索引时,也是需要占据物理空间的。而实际上当数据量比较大的时候,索引文件的大小也十分吓人。考虑到一个表上可能有多个索引、组合索引、数据行占用更小等情况,索引文件的大小可能达到物理盘中数据的1/10,甚至可达到1/3。

这就意味着索引无法全部装入内存之中。当通过索引对数据进行访问时,不可避免的需要对磁盘进行读写访问。同时我们还知道,内存的读写速度是磁盘的几个数量级。因此在对索引结构进行设计时要尽可能的减少对磁盘的读写次数,也就是所谓的磁盘 I/O 次数。

这也就是索引会采用 BTree 这种扁平树结构的原因,树的层数越少,磁盘I/O的次数自然就越少。不仅如此,我们上面提到过磁盘预读的局部性原理。根据这个原理再加上页表机制,能够在进行磁盘读取的时候更大化的提升性能。

BTree 相较于其它的二叉树结构,对磁盘的 I/O 次数已经非常少了。但是在实际的数据库应用中仍有些问题无法解决。

一是无法定位到数据行。通过 BTree 可以根据主键的排序定位出主键的位置,但是由于数据表的记录有多个字段,仅仅定位到主键是不够,还需要定位到数据行。虽然这个问题可以通过在 BTree 的节点中存储数据行或者增加定位的字段,但是这种方式会使得 BTree 的深度大幅度提高,从而也导致 I/O 次数的提高。

二是无法处理范围查询。在实际的应用中,数据库范围查询的频率非常高,而 BTree 只能定位到一个索引位置。虽然可以通过先后查询范围的左右界获得,但是这样的操作实际上无法很好的利用磁盘预读的局部性原理,先后查询可能会造成通过预读取的物理地址离散,使得 I/O 的效率并不高。

三是当数据量一大时,BTree的高度依旧会变得很高,搜索效率还是会大幅度的下降。

问题总是推动改进的前提。基于以上的问题考虑,就出现了对 BTree 的优化版本,也就是 B+Tree。

B+Tree索引

B+Tree 一看就是在 BTree 的基础上做了改进,那么到底改变了什么呢。废话不多说,先上图。

上图实际上是一种带有顺序索引的 B+Tree,与最基本的 B+Tree 的区别就在于叶子节点是否通过指针相连。一般数据库中常用的结构都是这种带有顺序索引的 B+Tree。后文探讨的也都是带顺序索引的 B+Tree 结构。

对比 BTree 和 B+Tree,我们可以发现二者主要在以下三个方面上的不同:

非叶子节点只存储键值信息,不再存储数据。

所有叶子节点之间都有一个链指针,指向下一个叶子节点。

数据都存放在叶子节点中。

看着 B+Tree,像不像是一颗树与一个有序链表的结合体。因而其实 B+Tree 也就是带有树和链表的部分优势。树结构使得有序检索更为简单,I/O 次数更少;有序链表结构使得可以按照键值排序的次序遍历全部记录。

B+Tree 在作为索引结构时能够带来的好处有:

一,I/O 次数更少。这是因为上文也说过,BTree 的节点是存放在内存页中的。那么在相同的内存页大小(一般为4k)的情况下,B+Tree 能够存储更多的键值,那么整体树结构的高度就会更小,需要的 I/O 次数也就越小。

二,数据遍历更为方便。这个优势很明显是由有序链表带来的。通过叶子节点的链接,使得对所有数据的遍历只需要在线性的链表上完成,这就非常适合区间检索和范围查询。

三,查询性能更稳定。这自然是由于只在叶子节点存储数据,所以所有数据的查询都会到达叶子节点,同时叶子节点的高度都相同,因此理论上来说所有数据的查询速度都是一致的。

正是由于 B+Tree 优秀的结构特性,使得常用作索引的实现结构。在 MySQL 中,存储引擎 MyISAM 和 InnoDB 都分别以 B+Tree 实现了响应的索引设计。

物理存储

虽说 B+Tree 结构都可以用在 MyISAM 和 InnoDB,但是这二者对索引的在物理存储层次的实现方式却不相同。InnoDB 实现的是聚簇索引,而 MyISAM 实现的却是非聚簇索引。在介绍聚簇索引之前,我们需要先了解以下啥是佩奇,不对,是啥是「主键索引」和「辅助索引」。

其实概念很简单。我们刚刚不是在讲 B+Tree 的时候说过,树的非叶子节点只存储键值。没错就是这个键值,当这个键值是数据表的主键时,那么所建立的就是主键索引;当这个键值是其它字段的时候,就是辅助索引。因而可以得出,主键索引只能有一个,而辅助索引却可以有很多个。

聚簇索引和非聚簇索引的区别也就是根据其对应的主键索引和辅助索引的不同特点而实现的。

聚簇索引

说回聚簇索引。先丢个定义。

聚簇索引的主键索引的叶子结点存储的是键值对应的数据本身;辅助索引的叶子结点存储的是键值对应的数据的主键键值。

这句话的信息量挺大的。首先,分析第一句话,主键索引的叶子节点存储的是键值对应的数据本身。

我们知道,主键索引存储的键值就是主键。那么也就是说,聚簇索引的主键索引,在叶子节点中存储的是主键和主键对应的数据。数据和主键索引是存储在一起的,一起作为叶子节点的一部分。

然后,分析第二句话,辅助索引的叶子结点存储的是键值对应的数据的主键键值。

我们又知道,辅助索引存储的键值是非主键的字段。那就也就是说,通过辅助索引,可以找到非主键字段对应的数据行中的主键。

重点来了。当然主键索引和辅助索引一结合,能干啥呢。当直接采用主键进行检索时,可通过主键索引直接获得数据;而当采用非主键进行检索时,先需要通过辅助索引来获得主键,然后再通过这个主键在主键索引中找到对应的数据行。

举个例子吧。假设有这么一个数据表。

那么采用聚簇索引的存储方式,对应的主键索引为:(主键为ID)

对应的辅助索引为:(键值为Name,大概的意思):

所以当使用where ID = 7这样的条件查找主键,则按照B+树的检索算法即可查找到对应的叶节点,之后获得行数据。对Name列进行条件搜索,则需要两个步骤:第一步在辅助索引B+树中检索Name,到达其叶子节点获取对应的主键。第二步使用主键在主键索引B+树种再执行一次B+树检索操作,最终到达叶子节点即可获取整行数据。

最后把以上过程整理总结一下,聚簇索引实际上的过程就分为以下两个过程。现在这个图应该能够看懂了吧。

非聚簇索引

学完了聚簇索引,非聚簇索引就简单多啦。同样,先上定义。

非聚簇索引的主键索引和辅助索引几乎是一样的,只是主索引不允许重复,不允许空值,他们的叶子结点都存储指向键值对应的数据的物理地址。

与聚簇索引来对比着看,上面的定义能够说明什么呢。首先,主键索引和辅助索引的叶子结点都存储着键值对应的数据的物理地址,这说明无论是主键索引还是辅助索引都能够通过直接获得数据,而不需要像聚簇索引那样在检索辅助索引时还得多绕一圈。

同时还说明一个点,叶子结点存储的是物理地址,那么表示数据实际上是存在另一个地方的,并不是存储在B+树的结点中。这说明非聚簇索引的数据表和索引表是分开存储的。

同样,对非聚簇索引的检索过程来个总结。

无论是主键索引还是辅助索引的检索过程,都只需要通过相应的 B+Tree 进行搜索即可获得数据对应的物理地址,然后经过依次磁盘 I/O 就可访问数据。

对比聚簇索引和非聚簇索引,可以发现二者最主要的区别就是在于是否在 B+Tree 的节点中存储数据,也就是数据和索引是否存储在一起。这个区别导致最大的问题就是聚簇索引的索引的顺序和数据本身的顺序是相同的,而非聚簇索引的顺序跟数据的顺序没有啥关系。

索引优化

介绍了这么多的索引,其实最终都是为了建立高性能的索引策略,对数据库中的索引进行优化。索引的优化有很多角度,针对特定的业务场景可采用不同的优化策略。这里考虑到文章篇幅,就不具体介绍,下次再出一篇专门讲索引优化的文章。简单列举一下在进行优化时可以考虑的几个方向:

1 独立的列。索引列不能是表达式的一部分,也不能是函数的参数。

2 前缀索引和索引选择性。这二者实际上是相互制约的关系,制约条件在于前缀的长度。一般应选择足够长的前缀以保证较高的选择性(保证查询效率),同时又不能太长以便节省空间。

3 尽量使用覆盖索引。覆盖索引是指一个索引包含所有需要查询的字段的值,这样在查询时只需要扫描索引而无须再去读取数据行,从而极大的提高性能。

4 使用索引扫描来做排序。要知道,扫描索引本身是很快的,在设计索引时,可尽可能的使用同一个索引既满足排序,又满足查找行数据。

最后,在建立索引时给几个小贴士:

1 优先使用自增key作为主键

2 记住最左前缀匹配原则

3 索引列不能参与计算

4 选择区分度高的列作索引

5 能扩展就不要新建索引

总结

索引的概念和原理是我们在了解和精通数据库过程中无法逃避的重点,而事实上建立高性能的索引对实际的应用场景也具有重要意义。本文的目的依旧是由浅入深的介绍索引这么个东西,从概念到实现再到最终的优化策略。

点到为止,学无止境。掌握了基本的理论和概念后,还需要在实际的服务器开发场景中针对具体的问题和服务进行索引优化方案的设计和使用。功力不够,仍需努力。

Reference

高性能 MySQL,Baron Schwartz 等人著,电子工业出版社

公众号码海系列文章:

https://www.jianshu.com/p/9e9aca844c13

https://www.runoob.com/mysql/mysql-index.html

https://www.cnblogs.com/Aiapple/p/5693239.html

https://blog.csdn.net/tongdanping/article/details/79878302

https://www.cnblogs.com/igoodful/p/9361500.html

别再一知半解啦,索引其实就这么回事

作者 | Amazing10

责编 | 屠敏

头图 | CSDN 下载自视觉中国

本文为「业余码农」投稿

索引的概念基本所有人都会遇到过,就算没有了解过数据库中的索引,在生活中也不可避免的接触到。比方说书籍的目录,字典的查询页,图书馆的科目检索等等。其实这些都是一种索引,并且所起到的作用大同小异。

而对于数据库而言,只不过是将索引的概念抽象出来,让建立索引的过程更为灵活而自由,从而可以在不同的场景下优化数据库的查询效率。

索引在数据库的实际应用场景中十分普遍,数据库的优化也离不开对索引的优化。同时,索引相关的知识也是面试高频的考点之一,是应试者理论结合现实最为直接的体现。

因此,本文将从基础理论出发,介绍 MySQL 按照逻辑角度的索引分类和实现,通过数据结构的实现原理阐述不同结构对建立索引带来的优劣势,同时针对物理存储的方式对索引的组织特点和应用场景进行分析。最后根据不同的应用场景尽可能的探究如何建立起高性能的索引。文章结构如下:

概念

什么是索引?

索引似乎并没有十分明确的定义,更多的是一种定性的描述。简单来讲,索引就是一种将数据库中的记录按照特殊形式存储的数据结构。通过索引,能够显著地提高数据查询的效率,从而提升服务器的性能。

专业一点来说呢,索引是一个排好序的列表,在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址。在数据库十分庞大的时候,索引可以大大加快查询的速度,这是因为使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数据对应的物理地址然后访问相应的数据。

说起索引,其实并不是 MySQL 数据库特有的机制,在关系型数据库中都会有类似不同的实现。这里我们也只是讨论 MySQL 数据库中的索引实现。

事实上,说是 MySQL 的索引其实并不准确。因为在 MySQL 中,索引是在存储引擎层而不是服务器层实现的。这意味着我们所讨论的索引准确来说是 InnoDB 引擎或 MyISAM 引擎或其它存储引擎所实现的。

所以索引即便是在 MySQL 中也没有统一的标准,不同存储引擎的所实现的索引工作方式也并不一样。不是所有的存储引擎都支持相同类型的索引,即便是多个引擎支持同一种类型的索引,其底层的实现也可能不同。

为什么需要索引

说了这么多,索引似乎就是给数据库添加了一个「目录页」,能够方便查询数据。但是索引的作用就仅此而已了吗,为什么需要大费周章的建立并优化索引?

说个题外话,我其实查字典从来都不喜欢查目录页,无论是查中文还是英文。因为觉得那样很慢,一个个找索引,效率很低。我习惯用的方式就是直接翻开字典,根据翻开的位置进行前后调整。比方说我想找「酱 JIANG」字,会先随机翻到一页,可能是「F」开头,在「J」前面,就往后翻一点;如果随机翻到「L」,那就往前翻一点。重复直至找到。

这大概就是类似于二分查找的方式,看起来好像是摆脱了索引的束缚,并且也能够获得比较高的查询效率。但是其实转念一想,在计算机的运行处理中,「一个个找索引」这个过程其实非常快,不能跟我们手动比对偏旁部首的效率相提并论。同时,为什么我可以直接翻开字典根据字母进行调整呢,这其实不就是因为我的脑子里存在一个大概的「索引表」,知道每个字母大概对应于字典的哪一个位置。虽然是模糊的,但却是真实存在的。(好不容易强行解释了一波...)

如此一来,可以看出索引的一大好处是如其概念中所提及的,使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数据对应的物理地址然后访问相应的数据。这样的方式自然减少了服务器在响应时所需要对数据库扫描的数据量。

不仅如此,在执行数据库的范围查询时,若不使用索引,那么MySQL会先扫描数据库的所有行数据并从中筛选出目标范围内的行记录,将这些行记录进行排序并生成一张临时表,然后通过临时表返回用户查询的目标行记录。这个过程会涉及到临时表的建立和行记录的排序,当目标行记录较多的时候,会大大影响范围查询的效率。

所以当添加索引时,由于索引本身具有的顺序性,使得在进行范围查询时,所筛选出的行记录已经排好序,从而避免了再次排序和需要建立临时表的问题。

同时,由于索引底层实现的有序性,使得在进行数据查询时,能够避免在磁盘不同扇区的随机寻址。使用索引后能够通过磁盘预读使得在磁盘上对数据的访问大致呈顺序的寻址。这本质上是依据局部性原理所实现的。

局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。程序运行期间所需要的数据通常比较集中。由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间) ,因此对于具有局部性的程序来说,磁盘预读可以提高I/O效率。

磁盘预读要求每次都会预读的长度一般为页的整数倍。而且数据库系统将一个节点的大小设为等于一个页,这样每个节点只需要一次 I/O 就可以完全载入。这里的页是通过页式的内存管理所实现的,概念在这里简单提一嘴。

分页机制就是把内存地址空间分为若干个很小的固定大小的页,每一页的大小由内存决定。这样做是为了从虚拟地址映射到物理地址,提高内存和磁盘的利用率。

所以呢,总结一下。索引的存在具有很大的优势,主要表现为以下三点:

索引大大减少了服务器需要扫描的数据量

索引可以帮助服务器避免排序和临时表

索引可以将随机 I/O 变成顺序 I/O

以上三点能够大大提高数据库查询的效率,优化服务器的性能。因此一般来说,为数据库添加高效的索引对数据库进行优化的重要工作之一。

不过,凡事都有两面性。索引的存在能够带来性能的提升,自然在其它方面也会付出额外的代价。

索引本身以表的形式存储,因此会占用额外的存储空间;

索引表的创建和维护需要时间成本,这个成本随着数据量增大而增大;

构建索引会降低数据的修改操作(删除,添加,修改)的效率,因为在修改数据表的同时还需要修改索引表;

所以对于非常小的表而言,使用索引的代价会大于直接进行全表扫描,这时候就并不一定非得使用索引了。没办法,成年人的世界总是这么的趋利避害。

逻辑分类

从逻辑的角度来对索引进行划分的话,可以分为单列索引、全文索引、组合索引和空间索引。其中单列索引又可分为主键索引、唯一索引和普通索引。这里的逻辑可以理解为从 SQL 语句的角度,或者是从数据库关系表的角度。下面就简单介绍这些索引的作用和用法,以及在修改表的时候如何添加索引。

主键索引

即主索引,根据主键建立索引,不允许重复,不允许空值;

主键:数据库表中一列或列组合(字段)的值,可唯一标识表中的每一行。

加速查询 + 列值唯一(不可以有)+ 表中只有一个

ALTER TABLE 'table_name' ADD PRIMARY KEY pk_index('col');

唯一索引

用来建立索引的列的值必须是唯一的,允许空值。唯一索引不允许表中任何两行具有相同的索引值。比方说,在 employee 表中职员的姓 name 上创建了唯一索引,那么就表示任何两个员工都不能同姓。

加速查询 + 列值唯一(可以有)

ALTER TABLE 'table_name' ADD UNIQUE index_name('col');

普通索引

用表中的普通列构建的索引,没有任何限制。

仅加速查询

ALTER TABLE 'table_name' ADD INDEX index_name('col');

全文索引

用大文本对象的列构建的索引

ALTER TABLE 'table_name' ADD FULLTEXT INDEX ft_index('col');

组合索引

用多个列组合构建的索引,这多个列中的值不允许有空值。

多列值组成一个索引,专门用于组合搜索,其效率大于索引合并。

ALTER TABLE 'table_name' ADD INDEX index_name('col1','col2','col3');

在对多列组合建立索引时,会遵循「最左前缀」原则。

最左前缀原则:顾名思义,就是最左优先,上例中我们创建了 (col1, col2, col3) 多列索引,相当于创建了 (col1) 单列索引,(col1, col2) 组合索引以及 (col1, col2, col3) 组合索引。

所以当我们在创建多列索引时,要根据业务场景,将 where 子句中使用最频繁的一列放在最左边。

空间索引

对空间数据类型的字段建立的索引,底层可通过 R 树实现。只不过使用较少,了解即可。

实现原理

我们知道,索引的底层本身就是通过数据结构来进行实现的。那么根据其底层的结构,常见的索引类型可分为哈希索引,BTree 索引,B+Tree 索引等。这里我们就主要来介绍这三种索引背后的实现机制。

哈希索引

顾名思义,哈希索引是通过哈希表实现的。哈希表的特点在之前的文章「九大数据结构」中已经详细介绍过。通过哈希表的键值之间的对应关系,能够在查询时精确匹配索引的所有列。哈希索引将所有的根据索引列计算出来的哈希码存储在索引中,同时将指向每个数据行的指针保存在哈希表中。

上图是通过哈希索引查询行数据的示意图,可以发现哈希索引同样会发生哈希冲突,并且是通过链地址法解决冲突的。当发送冲突时,还需要对链表进行遍历对比,才能够找到最终的结果。

在 MySQL 中,只有 Memory 存储引擎显式的支持哈希索引,而innodb是隐式支持哈希索引的。

这里的隐式支持是指,innodb引擎有一个特殊的功能 “自适应哈希索引”,当innodb注意到一些索引值被使用的非常频繁时,且符合哈希特点(如每次查询的列都一样),它会在内存中基于 B-Tree 索引之上再创建一个哈希索引。这样就让 BTree 索引也具有哈希索引的一些有点。这是一个完全自动的、内部的行为。

由于哈希结构的特殊性,其用于非常高的检索效率,通过哈希函数的映射可以一步到位。但是同样也是因为结构的特殊,导致哈希索引只适用于某些特定的场合。哈希索引的限制[1]:

不支持范围查询,比如 WHERE a > 5;只支持等值比较查询,包括=、IN 、<=>

无法被用来避免数据的排序操作;因为经过了哈希函数的映射过程,使得丢失了哈希前后的大小关系,从而无法按照索引值的顺序存储。

不支持部分索引列的匹配查找,因为哈希索引始终使用索引列的全部内容来计算哈希值。例如在数据列 (A, B) 上建立哈希索引,如果查询只有数据列 A,则无法使用该索引。

无法避免表扫描。因为当出现哈希冲突的时候,存储引擎必须遍历链表(拉链法)中所有的行指针,逐行进行比较,直到找到所有符合条件的行。

哈希冲突很多的情况下,其索引维护的代价很高,并且性能并不一定会比 BTree 索引高。

BTree 索引

BTree 实际上是一颗多叉平衡搜索树。从名字可以看出,BTree 首先是一颗多叉搜索树,这意味着它是具有顺序的;其次 BTree 还是平衡的,这意味着它的左右子树高度差小于等于1。

事实上一颗 BTree 需要满足以下几个条件:

每个叶子结点的高度都是一样的;

每个非叶子结点由 n-1 个 key 和 n 个指针 point 组成,其中 d<=n<=2d, key 和 point 相互间隔,结点两端一定是 key;

叶子结点指针都为 ;

非叶子结点的key都是 [key, data] 二元组,其中 key 表示作为索引的键,data 为键值所在行的数据;

一颗常见的BTree树见下图。

这是一颗三阶的BTree,可通过键值的大小排序进行数据的查询和检索,其中叶子节点的指针都为空,因此省略没画。从上图可以发现,BTree 的树形状相较于我们之前常见的二叉树等结构,更为扁平和矮胖。

之所以这样设计,还是跟磁盘读取的特点有关。我们知道在建立索引时,也是需要占据物理空间的。而实际上当数据量比较大的时候,索引文件的大小也十分吓人。考虑到一个表上可能有多个索引、组合索引、数据行占用更小等情况,索引文件的大小可能达到物理盘中数据的1/10,甚至可达到1/3。

这就意味着索引无法全部装入内存之中。当通过索引对数据进行访问时,不可避免的需要对磁盘进行读写访问。同时我们还知道,内存的读写速度是磁盘的几个数量级。因此在对索引结构进行设计时要尽可能的减少对磁盘的读写次数,也就是所谓的磁盘 I/O 次数。

这也就是索引会采用 BTree 这种扁平树结构的原因,树的层数越少,磁盘I/O的次数自然就越少。不仅如此,我们上面提到过磁盘预读的局部性原理。根据这个原理再加上页表机制,能够在进行磁盘读取的时候更大化的提升性能。

BTree 相较于其它的二叉树结构,对磁盘的 I/O 次数已经非常少了。但是在实际的数据库应用中仍有些问题无法解决。

一是无法定位到数据行。通过 BTree 可以根据主键的排序定位出主键的位置,但是由于数据表的记录有多个字段,仅仅定位到主键是不够,还需要定位到数据行。虽然这个问题可以通过在 BTree 的节点中存储数据行或者增加定位的字段,但是这种方式会使得 BTree 的深度大幅度提高,从而也导致 I/O 次数的提高。

二是无法处理范围查询。在实际的应用中,数据库范围查询的频率非常高,而 BTree 只能定位到一个索引位置。虽然可以通过先后查询范围的左右界获得,但是这样的操作实际上无法很好的利用磁盘预读的局部性原理,先后查询可能会造成通过预读取的物理地址离散,使得 I/O 的效率并不高。

三是当数据量一大时,BTree的高度依旧会变得很高,搜索效率还是会大幅度的下降。

问题总是推动改进的前提。基于以上的问题考虑,就出现了对 BTree 的优化版本,也就是 B+Tree。

B+Tree索引

B+Tree 一看就是在 BTree 的基础上做了改进,那么到底改变了什么呢。废话不多说,先上图。

上图实际上是一种带有顺序索引的 B+Tree,与最基本的 B+Tree 的区别就在于叶子节点是否通过指针相连。一般数据库中常用的结构都是这种带有顺序索引的 B+Tree。后文探讨的也都是带顺序索引的 B+Tree 结构。

对比 BTree 和 B+Tree,我们可以发现二者主要在以下三个方面上的不同:

非叶子节点只存储键值信息,不再存储数据。

所有叶子节点之间都有一个链指针,指向下一个叶子节点。

数据都存放在叶子节点中。

看着 B+Tree,像不像是一颗树与一个有序链表的结合体。因而其实 B+Tree 也就是带有树和链表的部分优势。树结构使得有序检索更为简单,I/O 次数更少;有序链表结构使得可以按照键值排序的次序遍历全部记录。

B+Tree 在作为索引结构时能够带来的好处有:

一,I/O 次数更少。这是因为上文也说过,BTree 的节点是存放在内存页中的。那么在相同的内存页大小(一般为4k)的情况下,B+Tree 能够存储更多的键值,那么整体树结构的高度就会更小,需要的 I/O 次数也就越小。

二,数据遍历更为方便。这个优势很明显是由有序链表带来的。通过叶子节点的链接,使得对所有数据的遍历只需要在线性的链表上完成,这就非常适合区间检索和范围查询。

三,查询性能更稳定。这自然是由于只在叶子节点存储数据,所以所有数据的查询都会到达叶子节点,同时叶子节点的高度都相同,因此理论上来说所有数据的查询速度都是一致的。

正是由于 B+Tree 优秀的结构特性,使得常用作索引的实现结构。在 MySQL 中,存储引擎 MyISAM 和 InnoDB 都分别以 B+Tree 实现了响应的索引设计。

物理存储

虽说 B+Tree 结构都可以用在 MyISAM 和 InnoDB,但是这二者对索引的在物理存储层次的实现方式却不相同。InnoDB 实现的是聚簇索引,而 MyISAM 实现的却是非聚簇索引。在介绍聚簇索引之前,我们需要先了解以下啥是佩奇,不对,是啥是「主键索引」和「辅助索引」。

其实概念很简单。我们刚刚不是在讲 B+Tree 的时候说过,树的非叶子节点只存储键值。没错就是这个键值,当这个键值是数据表的主键时,那么所建立的就是主键索引;当这个键值是其它字段的时候,就是辅助索引。因而可以得出,主键索引只能有一个,而辅助索引却可以有很多个。

聚簇索引和非聚簇索引的区别也就是根据其对应的主键索引和辅助索引的不同特点而实现的。

聚簇索引

说回聚簇索引。先丢个定义。

聚簇索引的主键索引的叶子结点存储的是键值对应的数据本身;辅助索引的叶子结点存储的是键值对应的数据的主键键值。

这句话的信息量挺大的。首先,分析第一句话,主键索引的叶子节点存储的是键值对应的数据本身。

我们知道,主键索引存储的键值就是主键。那么也就是说,聚簇索引的主键索引,在叶子节点中存储的是主键和主键对应的数据。数据和主键索引是存储在一起的,一起作为叶子节点的一部分。

然后,分析第二句话,辅助索引的叶子结点存储的是键值对应的数据的主键键值。

我们又知道,辅助索引存储的键值是非主键的字段。那就也就是说,通过辅助索引,可以找到非主键字段对应的数据行中的主键。

重点来了。当然主键索引和辅助索引一结合,能干啥呢。当直接采用主键进行检索时,可通过主键索引直接获得数据;而当采用非主键进行检索时,先需要通过辅助索引来获得主键,然后再通过这个主键在主键索引中找到对应的数据行。

举个例子吧。假设有这么一个数据表。

那么采用聚簇索引的存储方式,对应的主键索引为:(主键为ID)

对应的辅助索引为:(键值为Name,大概的意思):

所以当使用where ID = 7这样的条件查找主键,则按照B+树的检索算法即可查找到对应的叶节点,之后获得行数据。对Name列进行条件搜索,则需要两个步骤:第一步在辅助索引B+树中检索Name,到达其叶子节点获取对应的主键。第二步使用主键在主键索引B+树种再执行一次B+树检索操作,最终到达叶子节点即可获取整行数据。

最后把以上过程整理总结一下,聚簇索引实际上的过程就分为以下两个过程。现在这个图应该能够看懂了吧。

非聚簇索引

学完了聚簇索引,非聚簇索引就简单多啦。同样,先上定义。

非聚簇索引的主键索引和辅助索引几乎是一样的,只是主索引不允许重复,不允许空值,他们的叶子结点都存储指向键值对应的数据的物理地址。

与聚簇索引来对比着看,上面的定义能够说明什么呢。首先,主键索引和辅助索引的叶子结点都存储着键值对应的数据的物理地址,这说明无论是主键索引还是辅助索引都能够通过直接获得数据,而不需要像聚簇索引那样在检索辅助索引时还得多绕一圈。

同时还说明一个点,叶子结点存储的是物理地址,那么表示数据实际上是存在另一个地方的,并不是存储在B+树的结点中。这说明非聚簇索引的数据表和索引表是分开存储的。

同样,对非聚簇索引的检索过程来个总结。

无论是主键索引还是辅助索引的检索过程,都只需要通过相应的 B+Tree 进行搜索即可获得数据对应的物理地址,然后经过依次磁盘 I/O 就可访问数据。

对比聚簇索引和非聚簇索引,可以发现二者最主要的区别就是在于是否在 B+Tree 的节点中存储数据,也就是数据和索引是否存储在一起。这个区别导致最大的问题就是聚簇索引的索引的顺序和数据本身的顺序是相同的,而非聚簇索引的顺序跟数据的顺序没有啥关系。

索引优化

介绍了这么多的索引,其实最终都是为了建立高性能的索引策略,对数据库中的索引进行优化。索引的优化有很多角度,针对特定的业务场景可采用不同的优化策略。这里考虑到文章篇幅,就不具体介绍,下次再出一篇专门讲索引优化的文章。简单列举一下在进行优化时可以考虑的几个方向:

1 独立的列。索引列不能是表达式的一部分,也不能是函数的参数。

2 前缀索引和索引选择性。这二者实际上是相互制约的关系,制约条件在于前缀的长度。一般应选择足够长的前缀以保证较高的选择性(保证查询效率),同时又不能太长以便节省空间。

3 尽量使用覆盖索引。覆盖索引是指一个索引包含所有需要查询的字段的值,这样在查询时只需要扫描索引而无须再去读取数据行,从而极大的提高性能。

4 使用索引扫描来做排序。要知道,扫描索引本身是很快的,在设计索引时,可尽可能的使用同一个索引既满足排序,又满足查找行数据。

最后,在建立索引时给几个小贴士:

1 优先使用自增key作为主键

2 记住最左前缀匹配原则

3 索引列不能参与计算

4 选择区分度高的列作索引

5 能扩展就不要新建索引

总结

索引的概念和原理是我们在了解和精通数据库过程中无法逃避的重点,而事实上建立高性能的索引对实际的应用场景也具有重要意义。本文的目的依旧是由浅入深的介绍索引这么个东西,从概念到实现再到最终的优化策略。

点到为止,学无止境。掌握了基本的理论和概念后,还需要在实际的服务器开发场景中针对具体的问题和服务进行索引优化方案的设计和使用。功力不够,仍需努力。

Reference

高性能 MySQL,Baron Schwartz 等人著,电子工业出版社

公众号码海系列文章:

https://www.jianshu.com/p/9e9aca844c13

https://www.runoob.com/mysql/mysql-index.html

https://www.cnblogs.com/Aiapple/p/5693239.html

https://blog.csdn.net/tongdanping/article/details/79878302

https://www.cnblogs.com/igoodful/p/9361500.html

科研新手必读:一文搞定文献检索、下载、管理、阅读

导语

再过一个月,又有一批研究僧怀揣着拯救人类的“包袱”跳进科研这个坑了。

哦,不对不对,该积极起来。作为科研新人,导师和师兄师姐嘱咐你的第一句话就是:一定学会看文献,养成看文献的习惯。

今天要解决的是科研新人第一关:“怎么找到你需要的文献”;如何选择自己该读和值得读的文献以及阅读文献的正确打开方式。

恩,没错,啃文献是科研工作的第一道关,小编怎么忍心看着一脸懵逼无从下手的你们而不管呢?来,跟着小编一起来闯科研第一关吧!

如果您是导师,师姐或师兄,将此文转发给你身边的新人吧,帮您节省很多口水。

1

文献检索篇:Pubmeb为首的检索3巨头

看文献,第一步是要找到文献,这时候就要用文献检索工具了。

检索头牌:Pubmed

Pubmed作为美国国家医学图书馆所属的国家生物技术信息中心开发的一款论文搜索引擎,凭借其海量的文献数据和简便快捷的搜索方式,成为了网上使用最广泛的生物医学方面的文献搜索工具。我们可以通过最简单的在标题和摘要中搜寻相关的关键词或相关公式,来寻找相关的文章。

用之不易的Google学术

这个其实并不能算是文献检索工具,但其有个很大的特点就是能够对全文进行搜索,而不是像上面说的那两个只是搜索标题和摘要。因此当要搜索事实型依据的时候,比如,要搜索“某病的发病率为36%”这样的出处,在摘要中可能没有具体的数据,所以需要google来进行全文搜索。

Google学术的功能还是挺强大的,不过在天朝却被封了,要是想用还得翻墙。

不过我找了个方法,详见今天推送的第3条

关联检索:Web of Science

这个方法比较适合研究机构,因为Web of Science的数据库是要收费的,但其搜索引擎比Pubmed更高级,不但能够限定文章的学科,还能限定作者的国籍单位等等,非常好用。值得一提的是它里面的逻辑连接词比Pubmed多了一个很实用词——Near,这个能在相邻的两个句子中寻找关键词。比方说要搜索高血压和糖尿病的关系,如果使用一般“AND”来连接,可能会出现头一句是说的糖尿病,然后结尾出来个高血压,其实并无联系。但用“Near”的话,由于两个词之间的距离被限定了,因此相关的概率也会高的多。

中文检索:万方,知网,维普等

基本是中文文献,在google上找不到的话可以来这里试试。

2

文献下载篇:免费是王道

看文献少不了要下载全文,很多精彩论文却被挡在了收费门外,甚是不爽。“窃书不算偷”,如果有些工具可以帮助你免费浏览和下载文献,节约的不仅仅是钱,更重要得是提高了效率。

免费下载首先要知道就是sci-hub了,深谙这个道理的俄罗斯的女神经学家Elbakyan,她在2011年创办了一个网站——Sci-Hub,梦想是要通过这个网站,将世界上所有需要收费阅读的知识,免费提供给需要它们的人。而且她也真做到了,目前Sci-Hub收录了4700万篇学术文章,但是这个网站是“非法”,常常被“封禁”,不过它的生命力依然顽强,为了反“封禁”,网站采取了游击战的方式——换IP地址。现在还有人为了方便中国学者,整合“Sci-Hub中国版”。具体怎么使用,网上有很多文章教你怎么使用。

3

文献管理篇:Endnote 和它的七个小伙伴

说起文献管理,大天朝的小伙伴们首先默认想到Endnote,Endnote是一款功能强大的文献管理软件,可以非常方便地在word中插入所引用的文献。Endnote对英文文献的兼容性好,并且可以满足将中文文献插入word中, 是最经典的文献管理软件。

除Endnote 以外的7个文献下载软件

但在软件领域,它可不是独领风骚,一家独大,在国际科研界,数得上名号的还有另外七个软件:colwiz、F1000Workspace、Mendeley、Papers、ReadCube、RefME和Zotero。JEFFREY M. PERKEL在Nature上比较了这几个软件的核心功能,不想被Endnote挡住自己渊博知识储备之路的小伙伴们可以一探究竟。

这8个软件,有的擅长简化浏览和构建文献库流程,有的则擅长创建文献目录、通过共享工作空间或者推荐文献实现团队的协助合作,可谓各有所长。只是,F1000Workspace和Papers都是明摆着要给钱的,其他软件虽然某些功能受限,在开放那些免费功能已经够我等屌丝捯饬很久了。

每一个软件的存在都是为了帮助研究人员管理下载后杂乱无章、哪里方便存哪里、每到用时找不到的那些PDF文件。你说好不容易下意识的建立了一个文献文件夹吧,一打开简直爆满,每次下载的时候都重命名然后根据领域或者研究对象归类文件夹么?

如此细心活儿,臣妾做不到啊!好在参考文献管理软件可以通过索引电脑硬盘来帮你解决一派混乱的局面。特别是当你把一个PDF文献拖拽至应用软件窗口,会促发软件识别文献的DOI或者标题,从在线的服务器检索相关数据(比如标题、关键字和文献作者)。

4

下载后的文献怎么命名更高效

下载后的PDF文献很多很杂,怎么管理这些文献用起来方便呢?给这些PDF文件命名是门学问。根据文章题目?文献的标题大多很长啦,当你要找一篇文献的时候,你要一个一个点么?

那么,今天就要介绍一种高效的PDF命名方案,小伙伴不妨参考参考。

这个方案需要借助Endnote,在网上看到“心仪的”paper,不只要下载PDF,还要将文献导入Endnote。虽然工序是多了一道,但为了日后方便查阅,这是不可忽略的动作。这样文件里有一个pdf文件,EndNote里则对应着一个文献记录。

PDF文件的命名原则:选择能够唯一标识一篇paper的最少信息组合来做文件名。比较好的方式就是:期刊名缩写+卷号+首页。其他信息就免了。例如:J. Appl. Phys. 104 091901.pdf

这样的话,不需要看到文件名就大概知道这篇文章讲什么。为什么?因为文件夹里每一个PDF在EndNote里都有记录,在EndNote里浏览标题,遇到想看全文的,根据记录的期刊名、卷号和首页,就自然能在文件夹里找到相应的pdf文件。就算有两千多个文件,按Name排序,也能很快找到。

如果你在网上看到一篇文章,且感觉似曾相识,是不是已经下载过呢?这时通过EndNote就能很快查出来,设置文献在EndNote里排序方式,可以按Journal Name、Year、Volume、Page等进行排序。按Journal Name排有利于检查一篇paper你是否已经有了。按年份排则有利于写综述之前的文献阅读,方便你撰写最新的研究进展。

5

让文献主动找上门

每次检索某些热门领域的最新文献都麻烦的很,要定期去各大数据库或者杂志网站输入关键字后自己慢慢筛选,你说烦不烦!要是文献能主动送上门那才是一桩美事,别说,还真的可以,下面有两种“一劳永逸”的方法:

文献鸟Stork

文献鸟Stork是一款简单易上手的文献订阅工具,只要你把所有想跟踪的主题提炼出关键词,或者领域内大牛的人名输进去,就坐等通知吧!每天Stork会帮助你自动搜索然后把结果发给你,让你第一时间知道所关注领域的新文献,要是一次检索出的文献过多,Stork就会把每篇文章对应期刊的影响因子用颜色标记出来(颜色越黄,影响因子越高),帮你快速选择最值得阅读的文献。

首先在浏览器中输入网址Storkapp.me,界面出来后输入邮箱然后点击“sign up for free”:

网站会给你邮箱发链接设置密码、用户名等等,这里就不在赘述了,然后跳转到主页:

每输入一个关键字点击一下add,小伙伴们有没有注意到上图下半部分的Examples,是告诉你具体的关键字规则,比如输入stroke thrombolysis的意思是只要文献中包含stroke和thrombolysis,“stroke thrombolysis”则是把两个单词当成一个整体的短语进行搜索;你想要找Nature杂志的最新文献,不想附带的把只要包含了nature单词的文献都搜索出来的话,就在nature后面加上[Journal]即可,相信大家都是可以看懂的。这里添加了lncRNA、microbiota、CRISPR三个关键字,点了即时发送:

立马就收到邮件了!每个关键字都分paper和Awarded Grants,当然我们只关心paper啦!除了标记出影响因子,有免费全文的也会给予提示,没有免费全文的当然就用神器sci-hub(sci-hub.zz)了。

Pubmed文献订阅

Pubmed有订阅文献的功能?当然了!不过要提前注册下,在首页的右上角:

激活用户后,我们回到pubmed搜索,还是以CRISPR为例,点击search后,搜索框下方会出现create alert,点击后跳转到my NCBI页面,会询问你是否愿意接受CRISPR相关新文献的订阅推送,可以选择频率每月/每周/每天推送:

点击保存就可以坐等文献飞送到你眼前了。

6

四类必读文献

首先以下四种文献是需要阅读,且存在由浅入深的关系。

1.本领域核心期刊的文献——熟悉领域

不同的研究方向有不同的核心期刊,这里也不能一概唯IF是论了。当然,首先你要了解所研究的核心期刊有哪些,这个就要靠学长、老板或者网上战友的互相帮助了,挑选出几本价值高的作为备选。

2.本领域牛人或者主要课题组的文献——抓重点

每个领域都有几个所谓的领军人物,他们所从事的方向往往代表目前的发展主流。因此阅读这些组里的文献就可以把握目前的研究重点。那么,我怎么知道谁是牛人呢?这里我个人有两个小方法。

第一是在ISI 检索本领域的关键词,不要太多,这样你会查到很多文献,而后利用ISI 的refine 功能,就可以看到哪位作者发表的论文数量比较多,原则上一般发表论文数量较多的人和课题组就是这行里比较主要的了。

第二分方法,就是关注本领域规模较大的国际会议,看一看到关于会议的invited speaker的人,做邀请的报告人一般来说都是在该行有头有脸的人物了。

3.高引用次数的文章——追经典

一般来说高引用次数(如果不是靠自引堆上去的话)文章都是比较经典的文章,要么思路比较好,要么材料性能比较好,同时其文笔应该也不赖的话。多读这样的文章,体会作者对文章结构的把握和图表分析的处理,相信可以从中领悟很多东西的。

4.研究方向相近的文献——找同类

最后就是当你有了一定背景知识,开始做实验并准备写论文的时候需要看的文献了。首先要明确一点,你所做的实验想解决什么问题?是对原有材料的改进还是创造一种新的材料或者是新的制备方法,还是采用新的表征手段或是计算方法。明确这一点后,就可以有的放矢查找你需要的文献了。而且往往当你找到一篇与你研究方向相近的文章后,通过ISI 的反查,你可以找到引用它的文献和它引用的文献,从而建立一个文献树,更多的获取信息量。

7

不经过整理归类的文献

就不是自己的文献

很多时候我们下文献很盲目,好不容易下下来,却发现不是自己想要的,浪费时间又浪费磁盘空间,那么怎么快速识别是不是你想要的文献呢,告诉你一个比较简单实用的方法——“三点识别法”:

前言和最后一部分:一般这部分都是提出作者为什么要进行这项工作,依据和方法。

图表:提出采用的表征方法以及性能变化

结论:是否实现了既定目标以及是否需要改进

8

50字总结

精读一篇文献需要很长的时间,可以先尽可能用50 个字左右来归纳文章,说白了就是:

文章的目的(如改进某个性能或提出某种方法)+表征手段(如XRD,IR,TEM 等)+主要结论(如产物的性能)。

当你按照这个方法归纳整理几十篇文献后,自然会有一个大致的了解,而后再根据你的笔记将文献分类整理,当你在写论文需要解释引用时再回头精读,我觉得这样会提高效率不少。

9

文献阅读的正确姿势

以前拿到一篇新的文献,喜欢直接闷头苦看,谁要是这中间找我,都觉得我一副苦大仇深仿佛男朋友出柜了的表情。后来渐渐吃了不少苦头,总是没过两三天就把看好的内容还给杂志了,才悟出来看文献要带着问题一边思考一边研读。

初看文献的三个步骤:

1.先通读各个小标题,也就是results部分,弄清楚文章的思路和内在联系。

2. 看每个小标题对应的结果图和图注,了解文章通过什么手段来验证科学假设,不明白的地方进行标记。

3. 最后再把全文通读,根据做好的标记到文中找具体的解答,做好阅读摘要,注意作者在discussion部分的写作方式和经典句式。

通读文章前需要准备的问题:

1.文献解决了什么问题?是否发现了新的机制、新的biomarker还是新的药物作用靶点等等。

2. 这个问题重要么?为何重要?这点在introduction里面作者会提出交代,即文章研究的背景和意义。

3. 作者提出了什么假设来解决上述问题?理论依据是什么?

4. 作者通过哪些方法来验证他们的假设?你还知道其他什么方法?要注意你的实验室/你手头上可以实现其中哪些方法,以后遇到此类的问题就可以采用啦。

5. 这些方法是否符合论证假设的需要?是否不够全面还可以有其他的补充?还要考虑实验材料涉及到哪些?细胞、动物、临床是否都覆盖到了?

6. 作者为什么这样设计实验?有什么创新?实验证据是如何一步步得出的?

7. 所有的实验结果图都可以看懂吗?

8. 文章是如何描述结果、如何解析图表趋势,论据如何组合,如何表达自己的观点?

9. 写作上的亮点有哪些?

10. 文献的总思路图?(问题、设计、方法和讨论的逻辑关系是什么)

看完思考文献还没有解决的问题?即文献的减分项,机制是不是做的不透,创新性是否不足。要是自己接着此方向继续做,哪些是在我所在工作条件下可以做的,哪些必须要做,哪些别人肯定比我做得更好更快?

有人说过一篇文献最重要的部分依次是:图表,讨论,文字结果,方法,这也是不无道理的。对一个领域熟悉之后,能做到仅通过阅读图表及其说明文字即能把握文章的方法、结果,在最短的时间内把握最必要的信息的要求。或者发展到定期查新得到的文章只须看摘要、图表即可,个别涉及新方法或突破性结果,再看讨论,文字结果和方法。

如果时间充裕,discussion部分还是要重点研读一下,特别是遇到CNS级别的paper的时候。这部分会把文章的精髓,idea的创新性,作者的实验设计逻辑再阐述一遍,图表的趋势解析,论据的组合,写作的亮点都是可以学习的地方。

10

文献精读的三种思维

文献精读是科研人必不可少的功课。精读的主要目的是要把文章的内容真正消化掉,要转化为你自己可以运用的东西。精读的方式分为三种,它们包括“验证型阅读”、“挑刺型阅读”和“总结归纳型阅读”。

1.验证型阅读——模仿者

这时候,你的角色是一个模仿者。就是拿到一篇好文章,暂不看文字内容,先把图和表挑出来,根据图和表的内容想想你来写这篇文章,你会怎么写,包括大的结构框架、组织逻辑,以及如何引入问题,如何进行结果陈述,如何引申出结论等,然后再看作者是怎么做的,反复体会你想的和你看的有哪些不同。当你的想法和作者的雷同程度越来越高,你的水平也在不知不觉之中越来越高。

这种“图表——思索——验证”的阅读对提升思维非常有效。当你做实验后得到的就是几个表、几个图,如果有意识地经过了“验证型阅读”的训练,你就会知道该怎么从更高、更好的角度来写你的文章。同样的数据,有的人可以发10.0的文章,有的人可能只能发5.0的文章,为什么有这种差别?这种差别其实就是在平时阅读文献的过程中慢慢积累出来的。

2.挑刺型阅读——评论者

这时候,你的角色相当于一个评论者了。是一种更高层次的精读方式,需要有评论的一些基础。挑刺就是找不足,能挑刺说明你不仅消化了文章的内容,而且还能比作者看得更深、更远。很多高档次杂志经常会刊出针对新文章的评论性短文,这实际上就是一种挑刺型阅读。“挑刺型阅读”可以提供新的研究思路,如果你立志于从事科学研究,撰写课题申请书是必须的能力之一。课题申请最重要的就是有新的idea,“挑刺型阅读”可以为你今后从事科学研究的选题提供良好的训练。

3.归纳总结型阅读——专家型

这时候,你的角色则是一个高屋建瓴的专家了,是一种宏观视野下阅读,是对多个“单”研究的归纳性思考,是对本领域的研究现状和发展趋势的总结和把握。每个人都可以总结,但要真正总结到位并对发展趋势进行合理、准确的预测却需要很强的能力,一般初涉研究的硕士很难达到这一水平,“大牛”才能很好地做到这一点,不是大牛,不是专家也没关系啦,因为大牛不是天生的,也是从研究生开始慢慢修炼出来的,你可以尝试性按照这种思维去做,量变到质变,说不定哪一天你成大牛了。

这三种精读的思维模式,也反映了我们文献阅读的三种境界,某一天,你对归纳总结型阅读已经游刃有余了,那么恭喜你,你成为“大牛”了。

面试官:什么是覆盖索引?

覆盖索引

要查询的列被所使用的索引覆盖,即覆盖索引。

举个例子

假如有以下表 foo,其中 id 为主键索引,name 为普通索引

+----+--------+------+| id | name | sex |+----+--------+------+| 1 | 张三 | 男 || 2 | 李四 | 男 |+----+--------+------+

当执行 SQL :

select id from foo where name = '张三'

其执行计划为:

+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+--------------------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+--------------------------+| 1 | SIMPLE | foo | NULL | ref | idx_name | idx_name | 83 | const| 1 | 100.00 | Using index |+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+--------------------------+

当执行计划中的 Extra 为 Using index 时表示使用了覆盖索引,使用覆盖索引可以减少回表操作,提高 SQL 的查询性能

什么是回表?

当 select id 变成 select * 时则会进行回表操作,这是由 B+Tree 的存储特性决定的,此时执行计划中的 Using index 信息也消失了,在这种情况下 select * 比 select id 效率要低

select * from foo where name = '张三'B+Tree 的又是如何存储索引数据的?

请前往 如何干掉系统中的慢 SQL? 中阅读更多内容

微信大牛教你深入了解数据库索引

| 作者刘国斌,腾讯微信事业群研发工程师,目前从事企业微信的后台研发工作,已经参与企业微信消息系统、群聊、客户联系等企业微信多个核心功能的迭代。

数据库查询是数据库的最主要功能之一。我们都希望查询数据的速度能尽可能的快,因此数据库系统的设计者会从查询算法的角度进行优化。

最基本的查询算法当然是顺序查找(linear search),然而这种复杂度为O(n)的算法在数据量很大时显然是糟糕的,好在计算机科学的发展提供了很多更优秀的查找算法,例如二分查找(binary search)、二叉树查找(binary tree search)等。

如果稍微分析一下会发现,每种查找算法都只能应用于特定的数据结构之上,例如二分查找要求被检索数据有序,而二叉树查找只能应用于二叉查找树上,但是数据本身的组织结构不可能完全满足各种数据结构(例如,理论上不可能同时将两列都按顺序进行组织),所以,在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。

作为一种数据结构,索引专为加快查找速度而设计。索引本质上就是把一个关键字与它对应的记录相关联的过程,一个索引由若干个索引项组成,每个索引至少包含关键字和其对应的记录在存储器中的位置等信息。索引技术是组织大型数据库以及磁盘文件的一种重要技术。

PartⅠ 索引数据结构

索引主要分为线性索引、树形索引和多级索引。下面将简单介绍一些基础的索引实现方式以引出MySQL的索引实现。

1.1 线性索引

所谓线性索引就是将索引项集合组织为线性结构,也成为索引表。虽然MySQL常使用树形索引,但是线性索引的有些性质和树形索引是相通的,所以先简单介绍一下两种比较常见的线性索引。

1.1.1 稠密索引

稠密索引在线性索引中,将数据记得每一条记录都对应一个索引项,并维护一个索引表。由于在真实环境中可能需要应对大量的数据,所以对于稠密索引的索引表来说,索引项一定是按照关键码有序的排列。

索引项有序就意味着可以直接对索引表进行像二分查找、插值查找、斐波那契查找等高效率的查找操作,大大减少查询的时间复杂度。

但是稠密索引也有一个明显的缺点,对于大量数据状态下索引表会变得非常的长,如果全部取出内存的话将会占用大量内存空间。

1.1.2 分块索引

为了解决稠密索引带来的问题于是就有了分块索引。分块索引是稀疏索引的一种,把数据记得记录分成了若干块,,实现了分块有序,这些块满足以下两个条件:

第一个是块间有序,例如要求第二块所有记录的索引关键字均大于第一块中所有的记录关键字,第三块要大于第二块的。

第二个是块内无序,块内的记录可以瞎排虽然可以实现块内有序,但是这就造成了和稠密索引一样的问题。

对于分块有序的数据将每块建立一个索引项,这种索引方法叫做分块索引。分块索引的索引项结构可以如下:

最大关键字,用于存储每一块的最大关键字,可作为这一块所有数据的关键字范围的依据;

块记录个数,方便遍历块数据;

用于指向块首数据的指针,用于遍历块。

使用分块索引,即使是遍历索引列表再遍历对应的块,也要比直接遍历全部数据效率高,而且对于有序的索引列表还可以使用更高效的查找算法。总的来说,对细分快不需要有序的情况下,分块索引兼顾了空间效率和时间效率,大大增加了整体查找的速度,所以也被普遍运用于数据库表的查找技术应用中。

1.2 树形索引

1.2.1 二叉树、平衡二叉树和红黑树索引

线性索引在插入和删除上就要浪费大量的时间,这点对于数据量大的情况下尤为明显。所以MySQL的引擎几乎没有使用线性索引。MySQL的主流存储引擎都使用B-/B+树索引,作为二叉树的变种,我们先简单介绍一下使用二叉搜索树构建的索引。

二叉搜索树(binary search tree)又称为二叉查找树,它或者是一棵空树,要么是具有以下性质的二叉树:

若该树左子树不为空,则左子树上所有节点值均小于其根节点;

若该树右子树不为空,则右子树上所有节点值均大于其根节点;

该树的左右子树也是二叉搜索树。

一个可能的二叉树索引如下:

二叉树在保留了二分搜索的前提下,使插入和删除上可以直接修改指针实现,极大提高插入和删除的效率。但是由于二叉树的形状不确定性导致极端查询时间复杂度和平均查询时间复杂度相差很大。例如如果插入数据时本身就是从小到大插入的,那么在构建索引时就会构建出一条极右斜树,他虽然是二叉搜索树,但是这时候对索引的搜索效率等于遍历效率,即O(n),而对于上图情况却最多只需要查找两次。所以就有了平衡二叉树(AVL tree)和红黑树(Red–black tree)

平衡二叉树上所有结点的平衡因子(balance factor)值只能是-1、0、1,保证了查找时的效率问题,但是同样的由于频繁调整树的节点,插入和删除效率下降。而红黑树并不追求“完全平衡”——它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能。

红黑树能够以O(log2 n) 的时间复杂度进行搜索、插入、删除操作。此外,由于它的设计,任何不平衡都会在三次旋转之内解决。当然,还有一些更好的,但实现起来更复杂的数据结构,能够做到一步旋转之内达到平衡,但红黑树能够给我们一个比较“便宜”的解决方案。红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高。

1.3 B-/B+树索引

B树(B-树)是为了磁盘或其它存储设备而设计的一种多路平衡查找树,其本质就是使用[key, balue]结构构建出查找路线,key为记录的键值(即索引值),value对应不同数据记录。可以使用度和阶来定义B树。这里使用阶来定义:节点最大的孩子书目称为B树的阶。

一个m阶的B树具有如下性质:

如果根节点不是叶子节点,则其最少有两棵子树;

设(m/2) ≤ k ≤ m,则每个根的分支节点都有k-1个元素和k个孩子,每个叶子节点n都有k-1个元素;

所有叶子节点深度相同且等于树高h;每个非终端结点中包含有n个关键字信息:(n,P0,K1,P1,K2,P2,......,Kn,Pn)。其中:

Ki(i=1...n)为关键字,且关键字按顺序升序排序K(i-1) < Ki

Pi为指向子树根的接点,且指针P(i-1)指向子树种所有结点的关键字均小于Ki,但都大于K(i-1)

关键字的个数n必须满足(m/2)-1 <= n <= m-1

一个示例B树结构:

由于B-Tree的特性,在B-Tree中按key检索数据的算法非常直观:首先从根节点进行二分查找,如果找到则返回对应节点的data,否则对相应区间的指针指向的节点递归进行查找,直到找到节点或找到指针,前者查找成功,后者查找失败。伪代码参考如下:

BTree_Search(node, key) { if(node == ) return ; foreach(node.key) { if(node.key[i] == key) return node.data[i]; if(node.key[i] > key) return BTree_Search(point[i]->node); } return BTree_Search(point[i+1]->node);}data = BTree_Search(root, my_key);

关于B-Tree有一系列有趣的性质,例如一个度为d的B-Tree,设其索引N个key,则其树高h的上限为logd((N+1)/2),检索一个key,其查找节点个数的渐进复杂度为O(logdN)。从这点可以看出,B-Tree是一个非常有效率的索引数据结构。另外,由于插入删除新的数据记录会破坏B-Tree的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持B-Tree性质。

B树与红黑树最大的不同在于,B树的结点可以有许多子女,从几个到几千个。与红黑树一样,一棵含n个结点的B树的高度也为O(lgn),但可能比一棵红黑树的高度小许多,因为它的分支因子比较大。所以,B树可以在O(logn)时间内,实现各种如插入(insert),删除(delete)等动态集合操作。

B树解决了树深问题,但如果每个节点在磁盘的不同页,B树还是需要在硬盘的页面进行多次访问才能遍历所有元素, 为了解决这个问题就有了B+树,与B-Tree相比,B+Tree有以下不同点:

每个节点的指针上限为2d而不是2d+1;

分支节点类似索引key,只存储其子树中最大或者最小的节点;

叶子节点包含全部关键字信息及指向其的指针;

出现在分支节点的元素会被追加到该分支节点位置的中序后继者中。

一般在数据库系统或文件系统中使用的B+Tree结构都在经典B+Tree的基础上进行了优化,增加了顺序访问指针,如下图。

所以要遍历的时候直接使用链表,要查找的时候从树根查找。B+树的结构特别适合带有范围的查找,可以从根节点找到范围左边界的数据,然后再遍历到右边界即可。

PartⅡ MySQL中的B-TREE索引

2.1 MySIAM索引实现

MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。下图是MyISAM索引的原理图:

这里设表一共有三列,假设我们以Col1为主键,则图8是一个MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。如果我们在Col2上建立一个辅助索引,如图:

同样也是一颗B+Tree,data域保存数据记录的地址。因此,MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。

MyISAM的索引方式也叫做“非聚集”的,之所以这么称呼是为了与InnoDB的聚集索引区分。

2.2 InnoDB索引实现

InnoDB也使用B+Tree作为索引结构,但具体实现依然是有差异的。

第一个重大区别是InnoDB的数据文件本身就是索引文件。从上文知道,MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。

上图是InnoDB主索引(同时也是数据文件)的示意图,可以看到叶节点包含了完整的数据记录。这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。

第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域。例如,图11为定义在Col3上的一个辅助索引:

这里以英文字符的ASCII码作为比较准则。聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。

了解不同存储引擎的索引实现方式对于正确使用和优化索引都非常有帮助,例如知道了InnoDB的索引实现后,就很容易明白为什么不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。再例如,用非单调的字段作为主键在InnoDB中不是个好主意,因为InnoDB数据文件本身是一颗B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。

2.3 为什么使用B-/B+树

我们上面提到的所有索引数据结构,除了分块索引以外,其他的索引查找,都需要把所有的数据行都建立索引,索引索引本身会特别大,不可能全部存储在内存中。因此索引一般是以文件的形式存储在硬盘里。

2.3.1 主存存取

计算机的主存基本都是随机访问存储器(Random-Access Memory,RAM),其分为两类:静态随机访问存储器(SRAM)和动态随机访问存储器(DRAM)。SRAM比DRAM快,但是也贵的多,一般作为CPU的高速缓存,DRAM通常作为内存。这类存储器他们的结构和存储原理比较复杂,基本是使用电信号来保存信息的,不存在机器操作,所以访问速度非常快。他们是易失的,即如果断电,保存DRAM和SRAM保存的信息就会丢失。

2.3.2 磁盘存取

磁盘空间动辄百GB级的容量以及低廉的价格使得磁盘成为数据的主要存储区,但是磁盘存取相对于主存来说却多了一些额外的步骤导致效率远不如直接读写主存。因为磁盘涉及到机器操作,读取速度一般为毫秒级,从DRAM读速度比从磁盘度快10万倍,从SRAM读速度比从磁盘读快100万倍。下面来看下磁盘的结构以分析磁盘读写原理。

磁盘由盘片构成,每个盘片有两面,又称为盘面(Surface),这些盘面覆盖有磁性材料。盘片中央有一个可以旋转的主轴(spindle),他使得盘片以固定的旋转速率旋转,通常是5400转每分钟(Revolution Per Minute,RPM)或者是7200RPM。磁盘包含一个或多个这样的盘片并封装在一个密封的容器内。每个磁盘表面是由一组成为磁道(track)的同心圆组成的,每个磁道被划分为了一组扇区(sector)。每个扇区包含相等数量的数据位,通常是(512)子节。扇区之间由一些间隔(gap)隔开,间隔不存储数据。

每两个磁盘之间有一个传动臂,负责在其上磁盘的下盘面和其下磁盘的上盘面读取信息。所以如图中所示磁盘结构的话,有四个盘面用来存储数据。每个传动臂都有一个读写头用于读写磁道上扇区中的数据。完成一次磁盘读写,需要以下三个步骤:

寻道: 如图,传动臂沿着磁盘半径前后移动(实际是斜切向运动),驱动器可以将读写头定位到任何磁道上,每个磁头同一时刻也必须是同轴的,即从正上方向下看,所有磁头任何时候都是重叠的(不过目前已经有多磁头独立技术,可不受此限制)一般硬盘的平均寻道时间在7.5~14ms。磁盘地址 = 台号+柱面号+盘面号+扇区号;

旋转:读写头移动到指定磁道后,磁盘通过转动,将对应扇区移动到读写头下方(或上方,看盘面);

传送:数据通过系统总线传送到内存的时间,一般传输一个字节(byte)大概0.02us=2*10^(-8)s。

设完成一次磁盘读写的时间为T,则有:

T = t1 + t2 + t3

2.3.3 预读和局部性原理

由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,因此为了提高效率,要尽量减少磁盘I/O,减少读写操作。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理:

当一个数据被用到时,其附近的数据也通常会马上被使用;

程序运行期间所需要的数据通常比较集中。

由于不需要寻道时间,只需很少的旋转时间,所以磁盘顺序读取的效率很高,因此对于具有局部性的程序来说,预读可以提高I/O效率。

预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。MySQL中也有页的概念,而且一般也为页的整倍数。例如Innodb引擎中一页为16k。

2.3.4 B/B+树对于二叉树等索引的性能分析

综上所述,一般使用磁盘I/O次数评价索引结构的优劣。

从B-Tree分析,设由树高为h的m阶B树,根据B树的定义,可知检索一次最多需要访问h个节点。数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入。为了达到这个目的,在实际实现B-Tree还需要使用如下技巧:

通过控制B树的阶(或度),实现在新建节点时直接申请一个页的空间,保证一个节点物理上也存储在一个页里,由于计算机存储分配都是按页对齐的,就实现了一个node只需一次I/O。

由于根节点常驻内存,B-Tree中一次检索最多需要h-1次I/O,时间复杂度为O(h)=O(logm N)。一般实际应用中,阶数m是非常大的数字,通常超过100,因此树高h非常小(通常不超过3)。而红黑树这种结构,h明显要深的多。由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,所以红黑树的I/O时间复杂度也为O(h),效率明显比B-Tree差很多。

综上所述,用B-Tree作为索引结构效率是非常高的。

而且B+Tree更适合外存索引,原因和内节点阶数m有关。从上面分析可以看到,在一定范围内,m越大索引的性能越好,而出度的上限取决于节点内key和data的大小:

dmax=floor(pagesize/(keysize+datasize+pointsize))

floor表示向下取整。由于B+Tree内节点去掉了data域,因此可以拥有更大的出度,拥有更好的性能。

但是阶数并不能无限增长,否则会造成索引表不在同一页而降低效率,而且当阶数太大,在遍历某个索引节点时效率会退化为近似于顺序遍历。

2.4 B+树最左前缀原理

对于多级索引(a,b,c),对于 a > 0 and b = 1这种查询,不能用到索引,作为最左前缀原理。

在B+树构建时,其顺序是先以a的顺序排列,然后再以b的顺序排列,依次类推,所以如果对a进行范围查询,那么无法再次对其余两列进行匹配。

如果从左到右遍历一颗B+树索引,可能如下:

(1,a,x)(1,a,y)(1,b,x)(1,b,y)(1,c,x)(1,c,y)(2,a,x)(2,a,y)(2,b,x)(2,b,y)(2,c,x)(2,c,y)

PartⅢ 散列表查找(哈希查找)

B/B+树作为索引,所有只会访问或者修改一条数据的 SQL 的时间复杂度都是O(log n)。也就是树的高度,但是使用哈希却有可能达到 O(1) 的时间复杂度,看起来散列表查找效率特别美好。

但是如果我们使用哈希作为底层的数据结构,遇到order by、大于小于、更新索引列的值等场景时,使用哈希构成的主键索引或者辅助索引可能就没有办法快速处理了。

散列表对于处理范围查询或者排序性能会非常差,只能进行全表扫描并依次判断是否满足条件,也就意味着我们使用的索引对于这些查询没有其他任何效果,最终的性能可能都不如从日志中顺序进行匹配。

另外散列表的存储是尽量将数据打散到一个区间,打散的存储对于磁盘读写来说也是非常的不友好。因此对于MySQL的Innodb,只有少量读取请求会使用散列表优化读取速度,大部分情况,还是使用B+树。

科普:何为\"EI检索期刊\"?

何为"EI检索期刊"?

EI (工程索引)是全球范围内的一个数据库,主要收录工程技术领域的重要文献,包括期刊以及会议文献,另外也收录一些科技报告、专著等。EI收录包括三种类型:被EI核心收录、非核心收录(Pageone收录)、会议论文。

EI被称为全球核心,被每个国家认可。在中国,发表一篇EI检索论文,有些单位奖励3千到8千元不等。一般用作:硕士毕业、博士毕业、评副教授、评正教授使用。作者在国际会议或者国际杂志上发表论文被EI收录后,国内一些权威机构可以出具EI收录证书给作者。

检索类型

JA和CA的区别:

EI主要收录工程技术领域的重要文献,包括期刊以及会议文献,另外也收录一些科技报告、专著等。EI收录包括三种类型:被EI核心收录、非核心收录(Pageone收录)、会议论文。从2009年起已经没有核心检索和非核心检索的区别,全是核心检索。但是还分为期刊检索和会议检索。也就是源刊JA类型,会议CA类型。在职称评审和校科技部每年奖励中,只认可被EI核心收录的文章(EI光盘版)。

EI Village是一个数据库平台,包含两个数据库(EI和Inspec),所以首先把Inspec前面的勾去掉。下图(没有图)是区分JA类型和CA类型的方法。若判断为Document type: Conference article(CA),则为会议论文。若判断为Document type: Journal article(JA),则为期刊论文。

EI期刊源刊基本是JA类型。评职称时档次高于EI会议类型。

检索形式

Ei Compendex收录的文献涵盖了所有的工程领域,其中大约22%为会议文献,90%的文献语种是英文。Ei公司在1992年开始收录中国期刊。1998年Ei在清华大学图书馆建立了Ei中国镜像站。2009年以前,EI把它收录的论文分为两个档次 。

1 、EI Compendex 标引文摘 (也称核心数据)。它收录论文的题录、摘要,并以主题词、分类号进行标引深加工。有没有主题词和分类号是判断论文,是否被EI正式收录的唯一标志。

2 、EI Page One题录 (也称非核心数据)。主要以题录形式报到。有的也带有摘要,但未进行深加工,没有主题词和分类号。所以Page One 带有文摘不一定算做正式进入EI。

Ei Compendex数据库从2009年1月起,所收录的数据不再分核心数据和非核心数据,所以有数据都以是Ei Compendex的形式存在。EI数据库的收录的文献只分为两类:会议检索与期刊检索,其判断如下:

若判断为Document type: Conference article(CA),则为会议论文。若判断为Document type: Journal article(JA),则为期刊论文。

一般来说,期刊论文的质量与受认可程度高于会议论文的检索。