索引原理_索引原理图解 - CSDN
精华内容
参与话题
  • 数据库索引实现原理

    千次阅读 2019-04-15 17:29:04
    MySQL索引实现 在MySQL中,索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的,本文主要讨论...下图是MyISAM索引原理图: 图8 这里设表一共有三列,假设我们以Col1为主键,则图8是一个MyISA...

    MySQL索引实现

    在MySQL中,索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的,本文主要讨论MyISAM和InnoDB两个存储引擎的索引实现方式。

    MyISAM索引实现

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

    图8

     

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

    图9

     

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

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

    InnoDB索引实现

    虽然InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同。

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

    图10

     

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

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

    图11

     

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

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

    下一章将具体讨论这些与索引有关的优化策略。

    索引使用策略及优化

    MySQL的优化主要分为结构优化(Scheme optimization)和查询优化(Query optimization)。本章讨论的高性能索引策略主要属于结构优化范畴。本章的内容完全基于上文的理论基础,实际上一旦理解了索引背后的机制,那么选择高性能的策略就变成了纯粹的推理,并且可以理解这些策略背后的逻辑。

    示例数据库

    为了讨论索引策略,需要一个数据量不算小的数据库作为示例。本文选用MySQL官方文档中提供的示例数据库之一:employees。这个数据库关系复杂度适中,且数据量较大。下图是这个数据库的E-R关系图(引用自MySQL官方手册):

    图12
    图12

     

    最左前缀原理与相关优化

    高效使用索引的首要条件是知道什么样的查询会使用到索引,这个问题和B+Tree中的“最左前缀原理”有关,下面通过例子说明最左前缀原理。

    这里先说一下联合索引的概念。在上文中,我们都是假设索引只引用了单个的列,实际上,MySQL中的索引可以以一定顺序引用多个列,这种索引叫做联合索引,一般的,一个联合索引是一个有序元组<a1, a2, …, an>,其中各个元素均为数据表的一列,实际上要严格定义索引需要用到关系代数,但是这里我不想讨论太多关系代数的话题,因为那样会显得很枯燥,所以这里就不再做严格定义。另外,单列索引可以看成联合索引元素数为1的特例。

    以employees.titles表为例,下面先查看其上都有哪些索引:

    
     

    从结果中可以到titles表的主索引为<emp_no, title, from_date>,还有一个辅助索引<emp_no>。为了避免多个索引使事情变复杂(MySQL的SQL优化器在多索引时行为比较复杂),这里我们将辅助索引drop掉:

    这样就可以专心分析索引PRIMARY的行为了。

    情况一:全列匹配。

    很明显,当按照索引中所有列进行精确匹配(这里精确匹配指“=”或“IN”匹配)时,索引可以被用到。这里有一点需要注意,理论上索引对顺序是敏感的,但是由于MySQL的查询优化器会自动调整where子句的条件顺序以使用适合的索引,例如我们将where中的条件顺序颠倒:

    效果是一样的。

    情况二:最左前缀匹配。

    当查询条件精确匹配索引的左边连续一个或几个列时,如<emp_no>或<emp_no, title>,所以可以被用到,但是只能用到一部分,即条件所组成的最左前缀。上面的查询从分析结果看用到了PRIMARY索引,但是key_len为4,说明只用到了索引的第一列前缀。

    情况三:查询条件用到了索引中列的精确匹配,但是中间某个条件未提供。

    此时索引使用情况和情况二相同,因为title未提供,所以查询只用到了索引的第一列,而后面的from_date虽然也在索引中,但是由于title不存在而无法和左前缀连接,因此需要对结果进行扫描过滤from_date(这里由于emp_no唯一,所以不存在扫描)。如果想让from_date也使用索引而不是where过滤,可以增加一个辅助索引<emp_no, from_date>,此时上面的查询会使用这个索引。除此之外,还可以使用一种称之为“隔离列”的优化方法,将emp_no与from_date之间的“坑”填上。

    首先我们看下title一共有几种不同的值:

    只有7种。在这种成为“坑”的列值比较少的情况下,可以考虑用“IN”来填补这个“坑”从而形成最左前缀:

     这次key_len为59,说明索引被用全了,但是从type和rows看出IN实际上执行了一个range查询,这里检查了7个key。看下两种查询的性能比较:

    “填坑”后性能提升了一点。如果经过emp_no筛选后余下很多数据,则后者性能优势会更加明显。当然,如果title的值很多,用填坑就不合适了,必须建立辅助索引。

    情况四:查询条件没有指定索引第一列。

    由于不是最左前缀,索引这样的查询显然用不到索引。

    情况五:匹配某列的前缀字符串。

    此时可以用到索引,但是如果通配符不是只出现在末尾,则无法使用索引。(原文表述有误,如果通配符%不出现在开头,则可以用到索引,但根据具体情况不同可能只会用其中一个前缀)

    情况六:范围查询。

    范围列可以用到索引(必须是最左前缀),但是范围列后面的列无法用到索引。同时,索引最多用于一个范围列,因此如果查询条件中有两个范围列则无法全用到索引。

    可以看到索引对第二个范围索引无能为力。这里特别要说明MySQL一个有意思的地方,那就是仅用explain可能无法区分范围索引和多值匹配,因为在type中这两者都显示为range。同时,用了“between”并不意味着就是范围查询,例如下面的查询:

    看起来是用了两个范围查询,但作用于emp_no上的“BETWEEN”实际上相当于“IN”,也就是说emp_no实际是多值精确匹配。可以看到这个查询用到了索引全部三个列。因此在MySQL中要谨慎地区分多值匹配和范围匹配,否则会对MySQL的行为产生困惑。

    情况七:查询条件中含有函数或表达式。

    很不幸,如果查询条件中含有函数或表达式,则MySQL不会为这列使用索引(虽然某些在数学意义上可以使用)。例如:

    虽然这个查询和情况五中功能相同,但是由于使用了函数left,则无法为title列应用索引,而情况五中用LIKE则可以。再如:

    显然这个查询等价于查询emp_no为10001的函数,但是由于查询条件是一个表达式,MySQL无法为其使用索引。看来MySQL还没有智能到自动优化常量表达式的程度,因此在写查询语句时尽量避免表达式出现在查询中,而是先手工私下代数运算,转换为无表达式的查询语句。

    索引选择性与前缀索引

    既然索引可以加快查询速度,那么是不是只要是查询语句需要,就建上索引?答案是否定的。因为索引虽然加快了查询速度,但索引也是有代价的:索引文件本身要消耗存储空间,同时索引会加重插入、删除和修改记录时的负担,另外,MySQL在运行时也要消耗资源维护索引,因此索引并不是越多越好。一般两种情况下不建议建索引。

    第一种情况是表记录比较少,例如一两千条甚至只有几百条记录的表,没必要建索引,让查询做全表扫描就好了。至于多少条记录才算多,这个个人有个人的看法,我个人的经验是以2000作为分界线,记录数不超过 2000可以考虑不建索引,超过2000条可以酌情考虑索引。

    另一种不建议建索引的情况是索引的选择性较低。所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:

    Index Selectivity = Cardinality / #T

    显然选择性的取值范围为(0, 1],选择性越高的索引价值越大,这是由B+Tree的性质决定的。例如,上文用到的employees.titles表,如果title字段经常被单独查询,是否需要建索引,我们看一下它的选择性:

    title的选择性不足0.0001(精确值为0.00001579),所以实在没有什么必要为其单独建索引。

    有一种与索引选择性有关的索引优化策略叫做前缀索引,就是用列的前缀代替整个列作为索引key,当前缀长度合适时,可以做到既使得前缀索引的选择性接近全列索引,同时因为索引key变短而减少了索引文件的大小和维护开销。下面以employees.employees表为例介绍前缀索引的选择和使用。

    从图12可以看到employees表只有一个索引<emp_no>,那么如果我们想按名字搜索一个人,就只能全表扫描了:

    如果频繁按名字搜索员工,这样显然效率很低,因此我们可以考虑建索引。有两种选择,建<first_name>或<first_name, last_name>,看下两个索引的选择性:

    <first_name>显然选择性太低,<first_name, last_name>选择性很好,但是first_name和last_name加起来长度为30,有没有兼顾长度和选择性的办法?可以考虑用first_name和last_name的前几个字符建立索引,例如<first_name, left(last_name, 3)>,看看其选择性:

    选择性还不错,但离0.9313还是有点距离,那么把last_name前缀加到4:

    这时选择性已经很理想了,而这个索引的长度只有18,比<first_name, last_name>短了接近一半,我们把这个前缀索引 建上:

    此时再执行一遍按名字查询,比较分析一下与建索引前的结果:

    性能的提升是显著的,查询速度提高了120多倍。

    前缀索引兼顾索引大小和查询速度,但是其缺点是不能用于ORDER BY和GROUP BY操作,也不能用于Covering index(即当索引本身包含查询所需全部数据时,不再访问数据文件本身)。

    InnoDB的主键选择与插入优化

    在使用InnoDB存储引擎时,如果没有特别的需要,请永远使用一个与业务无关的自增字段作为主键。

    经常看到有帖子或博客讨论主键选择问题,有人建议使用业务无关的自增主键,有人觉得没有必要,完全可以使用如学号或身份证号这种唯一字段作为主键。不论支持哪种论点,大多数论据都是业务层面的。如果从数据库索引优化角度看,使用InnoDB引擎而不使用自增主键绝对是一个糟糕的主意。

    上文讨论过InnoDB的索引实现,InnoDB使用聚集索引,数据记录本身被存于主索引(一颗B+Tree)的叶子节点上。这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放,因此每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,如果页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点)。

    如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页。如下图所示:

    图13

     

    这样就会形成一个紧凑的索引结构,近似顺序填满。由于每次插入时也不需要移动已有数据,因此效率很高,也不会增加很多开销在维护索引上。

    如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置:

    图14

     

    此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。

    因此,只要可以,请尽量在InnoDB上采用自增字段做主键。

    本文参考:http://blog.codinglabs.org/articles/theory-of-mysql-index.html

    展开全文
  • 数据库索引原理到底是什么?

    万次阅读 2017-11-12 12:09:30
    引言  中小企业MIS系统的管理基本上由两大部份组成,一是前台的可视化操作,二是后台的数据库管理。网管对前台的管理和维护工作包括保障网络链路通畅、处理MIS终端的突发事件以及对操作员的管理、培训等,这是网管...

    引言

            中小企业MIS系统的管理基本上由两大部份组成,一是前台的可视化操作,二是后台的数据库管理。网管对前台的管理和维护工作包括保障网络链路通畅、处理MIS终端的突发事件以及对操作员的管理、培训等,这是网管们日常做得最多、最辛苦的功课;然而MIS系统架构中同等重要的针对数据库的管理、维护和优化工作,现实中似乎并没有得到网管朋友的足够重视,看起来这都是程序员的事,事实上,一个网管如果能在MIS设计期间就数据表的规范化、表索引优化、容量设计、事务处理等诸多方面与程序员进行卓有成效的沟通和协作,那么日常的前台管理工作将会变得大为轻松。因为在某种意义上,数据库管理系统就相当于操作系统,在系统中占有同样重要的位置。这正是SQL SERVER等数据库管理系统和dBASEX、ACCESS等数据库文件系统的本质区别,所以,对数据库管理系统操作能力的强弱在某种程度上也折射出了网管的水平——个人认为,称得上优秀的Admin,至少应该是一个称职的DBA(数据库管理员)。

            下面以SQL SERVER(下称 SQLS)为例,将数据库管理中难于理解的“索引原理”问题给各位朋友作一个深入浅出的介绍。其他的数据库管理系统如Oracle、Sybase等,朋友们可以融会贯通,举一反三。

    数据表的基本结构

            建立数据库的目的是管理大量数据,而建立索引的目的就是提高数据检索效率,改善数据库工作性能,提高数据访问速度。对于索引,我们要知其然,更要知其所以然,关键在于认识索引的工作原理,才能更好的管理索引。为认识索引工作原理,首先有必要对数据表的基本结构做一次全面的复习。

            SQLS当一个新表被创建之时,系统将在磁盘中分配一段以8K为单位的连续空间,当字段的值从内存写入磁盘时,就在这一既定空间随机保存,当一个8K空间用完的时候,SQLS指针会自动分配一个8K的空间。这里,每个8K空间被称为一个数据页(Page),又名页面或数据页面,并分配从0-7的页号,每个文件的第0页记录引导信息,叫文件头(File header);每8个数据页(64K)的组合形成扩展区(Extent),称为扩展,全部数据页的组合形成堆(Heap)

            SQLS规定行不能跨越数据页,所以,每行记录的最大数据量只能为8K。这就是char和varchar这两种字符串类型容量要限制在8K以内的原因。存储超过8K的数据应使用text类型,实际上,text类型的字段值不能直接录入和保存,它只是存储一个指针,指向由若干8K的文本数据页所组成的扩展区,真正的数据正是放在这些数据页中。

            页面有空间页面和数据页面之分。当一个扩展区的8个数据页中既包含了空间页面又包括了数据或索引页面时,称为混合扩展(Mixed Extent),每张表都以混合扩展开始;反之,称为一致扩展(Uniform Extent),专门保存数据及索引信息。

            表被创建之时,SQLS在混合扩展中为其分配至少一个数据页面,随着数据量的增长,SQLS可即时在混合扩展中分配出7个页面,当数据超过8个页面时,则从一致扩展中分配数据页面。

            空间页面专门负责数据空间的分配和管理,包括:PFS页面(Page free space):记录一个页面是否已分配、位于混合扩展还是一致扩展以及页面上还有多少可用空间等信息;GAM页面(Global allocation map)和SGAM页面(Secodary global allocation map):用来记录空闲的扩展或含有空闲页面的混合扩展的位置。SQLS综合利用这三种类型的页面文件在必要时为数据表创建新空间;

            数据页或索引页则专门保存数据及索引信息,SQLS使用4种类型的数据页面来管理表或索引:它们是IAM页、数据页、文本/图像页和索引页。

            在WINDOWS中,我们对文件执行的每一步操作,在磁盘上的物理位置只有系统(system)才知道;SQL SERVER沿袭了这种工作方式,在插入数据的过程中,不但每个字段值在数据页面中的保存位置是随机的,而且每个数据页面在“堆”中的排列位置也只有系统(system)才知道。这是为什么呢?众所周知,OS之所以能管理DISK,是因为在系统启动时首先加载了文件分配表:FAT(File Allocation Table),正是由它管理文件系统并记录对文件的一切操作,系统才得以正常运行;同理,作为管理系统级的SQL SERVER,也有这样一张类似FAT的表存在,它就是索引分布映像页:IAM(Index Allocation Map)。

            IAM的存在,使SQLS对数据表的物理管理有了可能。IAM页从混合扩展中分配,记录了8个初始页面的位置和该扩展区的位置,每个IAM页面能管理512,000个数据页面,如果数据量太大,SQLS也可以增加更多的IAM页,可以位于文件的任何位置。第一个IAM页被称为FirstIAM,其中记录了以后的IAM页的位置。

            数据页和文本/图像页互反,前者保存非文本/图像类型的数据,因为它们都不超过8K的容量,后者则只保存超过8K容量的文本或图像类型数据。而索引页顾名思义,保存的是与索引结构相关的数据信息。了解页面的问题有助我们下一步准确理解SQLS维护索引的方式,如页拆分、填充因子等。

    索引的基本概念

            索引是一种特殊类型的数据库对象,它与表有着密切的联系。索引是为检索而存在的。如一些书籍的末尾就专门附有索引,指明了某个关键字在正文中的出现的页码位置,方便我们查找,但大多数的书籍只有目录,目录不是索引,只是书中内容的排序,并不提供真正的检索功能。可见建立索引要单独占用空间索引也并不是必须要建立的,它们只是为更好、更快的检索和定位关键字而存在。

            再进一步说,我们要在图书馆中查阅图书,该怎么办呢?图书馆的前台有很多叫做索引卡片柜的小柜子,里面分了若干的类别供我们检索图书,比如你可以用书名的笔画顺序或者拼音顺序作为查找的依据,你还可以从作者名的笔画顺序或拼音顺序去查询想要的图书,反正有许多检索方式,但有一点很明白,书库中的书并没有按照这些卡片柜中的顺序排列——虽然理论上可以这样做,事实上,所有图书的脊背上都人工的粘贴了一个特定的编号①,它们是以这个顺序在排列。索引卡片中并没有指明这本书摆放在书库中的第几个书架的第几本,仅仅指明了这个特定的编号。管理员则根据这一编号将请求的图书返回到读者手中。这是很形象的例子,以下的讲解将会反复用到它。(key-value)

            SQLS在安装完成之后,安装程序会自动创建master、model、tempdb等几个特殊的系统数据库,其中master是SQLS的主数据库,用于保存和管理其它系统数据库、用户数据库以及SQLS的系统信息,它在SQLS中的地位与WINDOWS下的注册表相当。master中有一个名为sysindexes的系统表,专门管理索引。SQLS查询数据表的操作都必须用到它,毫无疑义,它是本文主角之一。查看一张表的索引属性,可以在查询分析器中使用以下命令:select * from sysindexes where id=object_id(‘tablename’) ;而要查看表的索引所占空间的大小,可以使用系统存储过程命令:sp_spaceused tablename,其中参数tablename为被索引的表名。

    平衡树

            如果你通过书后的索引知道了一个关键字所在的页码,你有可能通过随机的翻寻,最终到达正确的页码。但更科学更快捷的方法是:首先把书翻到大概二分之一的位置,如果要找的页码比该页的页码小,就把书向前翻到四分之一处,否则,就把书向后翻到四分之三的地方,依此类推,把书页续分成更小的部分,直至正确的页码。这叫“两分法”,微软在官方教程MOC里另有一种说法:叫B树(B-Tree,Balance Tree),即平衡树。

            一个表索引由若干页面组成,这些页面构成了一个树形结构。B树由“根”(root)开始,称为根级节点,它通过指向另外两个页,把一个表的记录从逻辑上分成两个部分:“枝”—--非叶级节点(Non-Leaf Level);而非叶级节点又分别指向更小的部分:“叶”——叶级节点(Leaf Level)。根节点、非叶级节点和叶级节点都位于索引页中,统称为索引节点,属于索引页的范筹。这些“枝”、“叶”最终指向了具体的数据页(Page)。在根级节点和叶级节点之间的叶又叫数据中间页。

            “根”(root)对应了sysindexes表的Root字段,其中记载了非叶级节点的物理位置(即指针);非叶级节点位于根节点和叶节点之间,记载了指向叶级节点的指针;而叶级节点则最终指向数据页。这就是“平衡树”。(这里好像说的是B+树?)

    聚集索引和非聚集索引

            从形式上而言,索引分为聚集索引(Clustered Indexes)和非聚集索引(NonClustered Indexes)。聚集索引中键值的逻辑顺序决定了表中相应行的物理顺序,在非聚集索引中,数据库表中记录的物理顺序与索引顺序可以不相同。

            聚集索引相当于书籍脊背上那个特定的编号。如果对一张表建立了聚集索引,其索引页中就包含着建立索引的列的值(下称索引键值),那么表中的记录将按照该索引键值进行排序。比如,我们如果在“姓名”这一字段上建立了聚集索引,则表中的记录将按照姓名进行排列;如果建立了聚集索引的列是数值类型的,那么记录将按照该键值的数值大小来进行排列。

            非聚集索引用于指定数据的逻辑顺序,也就是说,表中的数据并没有按照索引键值指定的顺序排列,而仍然按照插入记录时的顺序存放。其索引页中包含着索引键值和它所指向该行记录在数据页中的物理位置,叫做行定位符(RID:Row ID)。好似书后面的的索引表,索引表中的顺序与实际的页码顺序也是不一致的。而且一本书也许有多个索引。比如主题索引和作者索引。

            SQL Server在默认的情况下建立的索引是非聚集索引,由于非聚集索引不对表中的数据进行重组,而只是存储索引键值并用一个指针指向数据所在的页面。一个表如果没有聚集索引时,理论上可以建立249个非聚集索引。每个非聚集索引提供访问数据的不同排序顺序。

            与非聚集索引相比,聚集索引通常提供更快的数据访问速度。聚集索引更适用于对很少对基表进行增删改操作的情况。聚集索引表记录的排列顺序与索引的排列顺序一致,优点是查询速度快,因为一旦具有第一个索引值的纪录被找到,具有连续索引值的记录也一定物理的紧跟其后(聚合索引存储记录是物理上连续存在的)。聚集索引的缺点是对表进行修改速度较慢,这是为了保持表中的记录的物理顺序与索引的顺序一致,而把记录插入到数据页的相应位置,必须在数据页中进行数据重排,降低了执行速度。非聚集索引指定了表中记录的逻辑顺序,但记录的物理顺序和索引的顺序不一致,聚集索引和非聚集索引都采用了B+树的结构,但非聚集索引的叶子层并不与实际的数据页相重叠,而采用叶子层包含一个指向表中的记录在数据页中的指针的方式。非聚集索引比聚集索引层次多,添加记录不会引起数据顺序的重组。

            下面的表总结了何时使用聚集索引或非聚集索引:

             

    数据是如何被访问的

            若能真正理解了以上索引的基础知识,那么再回头来看索引的工作原理就简单和轻松多了。

    SQLS怎样访问没有建立任何索引数据表:

            Heap译成汉语叫做“堆”,其本义暗含杂乱无章、无序的意思,前面提到数据值被写进数据页时,由于每一行记录之间并没地有特定的排列顺序,所以行与行的顺序就是随机无序的,当然表中的数据页也就是无序的了,而表中所有数据页就形成了“堆”,可以说,一张没有索引的数据表,就像一个只有书柜而没有索引卡片柜的图书馆,书库里面塞满了一堆乱七八糟的图书。当读者对管理员提交查询请求后,管理员就一头钻进书库,对照查找内容从头开始一架一柜的逐本查找(顺序遍历),运气好的话,在第一个书架的第一本书就找到了,运气不好的话,要到最后一个书架的最后一本书才找到。

            SQLS在接到查询请求的时候,首先会分析sysindexes表中一个叫做索引标志符(INDID: Index ID)的字段的值,如果该值为0,表示这是一张数据表而不是索引表,SQLS就会使用sysindexes表的另一个字段——也就是在前面提到过的FirstIAM值中找到该表的IAM页链——也就是所有数据页集合。这就是对一个没有建立索引的数据表进行数据查找的方式,是不是很没效率?对于没有索引的表,对于一“堆”这样的记录,SQLS也只能这样做,而且更没劲的是,即使在第一行就找到了被查询的记录,SQLS仍然要从头到尾的将表扫描一次。这种查询称为“遍历”,又叫“表扫描”。

            可见没有建立索引的数据表照样可以运行,不过这种方法对于小规模的表来说没有什么太大的问题,但要查询海量的数据效率就太低了。

    SQLS怎样访问建立了非聚集索引的数据表:

            如前所述,非聚集索引可以建多个具有B树结构,其叶级节点不包含数据页,只包含索引行

            假定一个表中只有非聚集索引,则每个索引行包含了非聚集索引键值以及行定位符(ROW ID,RID),他们指向具有该键值的数据行。每一个RID由文件ID、页编号和在页中行的编号组成。当INDID的值在2-250之间时,意味着表中存在非聚集索引页。此时,SQLS调用ROOT字段的值指向非聚集索引B树的ROOT,在其中查找与被查询最相近的值,根据这个值找到在非叶级节点中的页号,然后顺藤摸瓜,在叶级节点相应的页面中找到该值的RID,最后根据这个RID在Heap中定位所在的页和行并返回到查询端。

            例如:假定在Lastname上建立了非聚集索引,则执行Select * From Member Where Lastname=’Ota’时,查询过程是:①SQLS查询INDID值为2;②立即从根出发,在非叶级节点中定位最接近Ota的值“Martin”,并查到其位于叶级页面的第61页;③仅在叶级页面的第61页的Martin下搜寻Ota的RID,其RID显示为N∶706∶4,表示Lastname字段中名为Ota的记录位于堆的第707页的第4行,N表示文件的ID值,与数据无关;④根据上述信息,SQLS立马在堆的第 707页第4行将该记录“揪”出来并显示于前台(客户端)。视表的数据量大小,整个查询过程费时从百分之几毫秒到数毫秒不等。

            在谈到索引基本概念的时候,我们就提到了这种方式:

            图书馆的前台有很多索引卡片柜,里面分了若干的类别,诸如按照书名笔画或拼音顺序、作者笔画或拼音顺序等等,但不同之处有二:① 索引卡片上记录了每本书摆放的具体位置——位于某柜某架的第几本——而不是“特殊编号”;② 书脊上并没有那个“特殊编号”。管理员在索引柜中查到所需图书的具体位置(RID)后,根据RID直接在书库中的具体位置将书提出来。显然,这种查询方式效率很高,但资源占用极大,因为书库中书的位置随时在发生变化,必然要求管理员花费额外的精力和时间随时做好索引更新。

    SQLS怎样访问建立了聚集索引的数据表:

            在聚集索引中,数据所在的数据页是叶级,索引数据所在的索引页是非叶级。查询原理和上述对非聚集索引的查询相似,但由于记录是按照聚集索引中索引键值进行排序,换句话说,聚集索引的索引键值也就是具体的数据页。

            这就好比书库中的书就是按照书名的拼音在排序,而且也只按照这一种排序方式建立相应的索引卡片,于是查询起来要比上述只建立非聚集索引的方式要简单得多。仍以上面的查询为例:

            假定在Lastname字段上建立了聚集索引,则执行Select * From Member Where Lastname=’Ota’时,查询过程是:①SQLS查询INDID值为1,这是在系统中只建立了聚集索引的标志;②立即从根出发,在非叶级节点中定位最接近Ota的值“Martin”,并查到其位于叶级页面的第120页;③在位于叶级页面第120页的Martin下搜寻到Ota条目,而这一条目已是数据记录本身;④将该记录返回客户端。

            这一次的效率比第二种方法更高,以致于看起来更美,然而它最大的优点也恰好是它最大的缺点——由于同一张表中同时只能按照一种顺序排列,所以在任何一种数据表中的聚集索引只能建立一个;并且建立聚集索引需要至少相当于源表120%的附加空间,以存放源表的副本和索引中间页!

            难道鱼和熊掌就不能兼顾了吗?办法是有的。

    SQLS怎样访问既有聚集索引、又有非聚集索引的数据表:

            如果我们在建立非聚集索引之前先建立了聚集索引的话,那么非聚集索引就可以使用聚集索引的关键字进行检索,就像在图书馆中,前台卡片柜中的可以有不同类别的图书索引卡,然而每张卡片上都载明了那个特殊编号——并不是书籍存放的具体位置。这样在最大程度上既照顾了数据检索的快捷性,又使索引的日常维护变得更加可行,这是最为科学的检索方法。也就是说,在只建立了非聚集索引的情况下,每个叶级节点指明了记录的行定位符(RID);而在既有聚集索引又有非聚集索引的情况下,每个叶级节点所指向的是该聚集索引的索引键值,即数据记录本身。

            假设聚集索引建立在Lastname上,而非聚集索引建立在Firstname上,当执行Select * From Member Where Firstname=’Mike’时,查询过程是:①SQLS查询INDID值为2;②立即从根出发,在Firstname的非聚集索引的非叶级节点中定位最接近Mike的值“Jose”条目;③从Jose条目下的叶级页面中查到Mike逻辑位置——不是RID而是聚集索引的指针;④根据这一指针所指示位置,直接进入位于Lastname的聚集索引中的叶级页面中到达Mike数据记录本身;⑤将该记录返回客户端。

            这就完全和我们在“索引的基本概念”中讲到的现实场景完全一样了,当数据发生更新的时候,SQLS只负责对聚集索引的健值加以维护,而不必考虑非聚集索引,只要我们在ID类的字段上建立聚集索引,而在其它经常需要查询的字段上建立非聚集索引,通过这种科学的、有针对性的在一张表上分别建立聚集索引和非聚集索引的方法,我们既享受了索引带来的灵活与快捷,又相对规避了维护索引所导致的大量的额外资源消耗。

    索引的优点和不足

            索引有一些先天不足:

    • 建立索引,系统要占用大约为表的1.2倍的硬盘和内存空间来保存索引。
    • 更新数据的时候,系统必须要有额外的时间来同时对索引进行更新,以维持数据和索引的一致性——这就如同图书馆要有专门的位置来摆放索引柜,并且每当库存图书发生变化时都需要有人将索引卡片重整以保持索引与库存的一致。

            当然建立索引的优点也是显而易见的:在海量数据的情况下,如果合理的建立了索引,则会大大加强SQLS执行查询、对结果进行排序、分组的操作效率。实践表明,不恰当的索引不但于事无补,反而会降低系统性能。因为大量的索引在进行插入、修改和删除操作时比没有索引花费更多的系统时间。比如在如下字段建立索引应该是不恰当的:1、很少或从不引用的字段;2、逻辑型的字段,如男或女(是或否)等。

            综上所述,提高查询效率是以消耗一定的系统资源为代价的,索引不能盲目的建立,必须要有统筹的规划,一定要在“加快查询速度”与“降低修改速度”之间做好平衡,有得必有失,此消则彼长。这是考验一个DBA是否优秀的很重要的指标。

    SQLS中对索引的管理和优化

            至此,我们一直在说SQLS在维护索引时要消耗系统资源,那么SQLS维护索引时究竟消耗了什么资源?会产生哪些问题?究竟应该才能优化字段的索引?以下就从七个方面来回答这些问题:

    页分裂

            微软MOC教导我们:当一个数据页达到了8K容量,如果此时发生插入或更新数据的操作,将导致页的分裂(又名页拆分):

    • 有聚集索引的情况下:聚集索引将被插入和更新的行指向特定的页,该页由聚集索引关键字决定;    
    • 只有堆的情况下:只要有空间就可以插入新的行,但是如果我们对行数据的更新需要更多的空间,以致大于了当前页的可用空间,行就被移到新的页中,并且在原位置留下一个转发指针,指向被移动的新行,如果具有转发指针的行又被移动了,那么原来的指针将重新指向新的位置; 
    •  如果堆中有非聚集索引,那么尽管插入和更新操作在堆中不会发生页分裂,但是在非聚集索引上仍然产生页分裂;(非聚集索引存储时会按索引字段进行排序,因此页满也会分裂)
            无论有无索引,大约一半的数据将保留在老页面,而另一半将放入新页面,并且新页面可能被分配到任何可用的页。所以,频繁页分裂,后果很严重,将使物理表产生大量数据碎片,导致直接造成I/O效率的急剧下降,最后,停止SQLS的运行并重建索引将是我们的唯一选择!

    填充因子

            然而在“混沌之初”,就可以在一定程度上避免不愉快出现:在创建索引时,可以为这个索引指定一个填充因子,以便在索引的每个叶级页面上保留一定百分比的空间,将来数据可以进行扩充和减少页分裂。填充因子是从0到100的百分比数值,设为100时表示将数据页填满。只有当不会对数据进行更改时(例如只读表中)才用此设置。值越小则数据页上的空闲空间越大,这样可以减少在索引增长过程中进行页分裂的需要,但这一操作需要占用更多的硬盘空间。

            填充因子只在创建索引时执行,索引创建以后,当表中进行数据的添加、删除或更新时,是不会保持填充因子的,如果想在数据页上保持额外的空间,则有悖于使用填充因子的本意,因为随着数据的输入,SQLS必须在每个页上进行页拆分,以保持填充因子指定的空闲空间。因此,只有在表中的数据进行了较大的变动,才可以填充数据页的空闲空间。这时,可以从容的重建索引,重新指定填充因子,重新分布数据。反之,填充因子指定不当,就会降低数据库的读取性能,其降低量与填充因子设置值成反比。例如,当填充因子的值为50时,数据库的读取性能会降低两倍!所以,只有在表中根据现有数据创建新索引,并且可以预见将来会对这些数据进行哪些更改时,设置填充因子才有意义。

    贴切实例的数学题

            假定数据库设计没有问题,那么是否象上篇中分析的那样,当你建立了众多的索引,在查询工作中SQLS就只能按照“最高指示”用索引处理每一个提交的查询呢?答案是否定的!上篇“数据是怎样被访问的”章节中提到的四种索引方案只是一种静态的、标准的和理论上的分析比较,实际上,将在外,军令有所不从,SQLS几乎完全是“自主”的决定是否使用索引或使用哪一个索引!

            让我们先来算一道题:如果某表的一条记录在磁盘上占用1000字节(1K)的话,我们对其中10字节的一个字段建立索引,那么该记录对应的索引大小只有10字节(0.01K)。上篇说过,SQLS的最小空间分配单元是“页(Page)”,一个页面在磁盘上占用8K空间,所以一页只能存储8条“记录”,但可以存储800条“索引”。现在我们要从一个有8000条记录的表中检索符合某个条件的记录(有Where子句),如果没有索引的话,我们需要遍历8000条×1000字节/8K字节=1000个页面才能够找到结果。如果在检索字段上有上述索引的话,那么我们可以在8000条×10字节/8K字节=10个页面中就检索到满足条件的索引块,然后根据索引块上的指针逐一找到结果数据块,这样I/O访问量肯定要少得多。

            然而有时用索引还不如不用索引快!同上,如果要无条件检索全部记录(不用Where子句),不用索引的话,需要访问8000条×1000字节/8K字节=1000个页面;而使用索引的话,首先检索索引,访问8000条×10字节/8K字节=10个页面得到索引检索结果,再根据索引检索结果去对应数据页面,由于是检索全部数据,所以需要再访问8000条×1000字节/8K字节=1000个页面将全部数据读取出来,一共访问了1010个页面,这显然不如不用索引快。

            SQLS内部有一套完整的数据索引优化技术,在上述情况下,SQLS会自动使用表扫描的方式检索数据而不会使用任何索引。那么SQLS是怎么知道什么时候用索引,什么时候不用索引的呢?因为SQLS除了维护数据信息外,还维护着数据统计信息

    统计信息

            打开企业管理器,单击“Database”节点,右击Northwind数据库→单击“属性”→选择“Options”选项卡,观察“Settings”下的各项复选项,你发现了什么?从Settings中我们可以看到,在数据库中,SQLS将默认的自动创建和更新统计信息,这些统计信息包括数据密度和分布信息,正是它们帮助SQLS确定最佳的查询策略:建立查询计划和是否使用索引以及使用什么样的索引。在创建索引时,SQLS会创建分布数据页来存放有关索引的两种统计信息:分布表和密度表。查询优化器使用这些统计信息估算使用该索引进行查询的成本(Cost),并在此基础上判断该索引对某个特定查询是否有用。

            随着表中的数据发生变化,SQLS自动定期更新这些统计信息。采样是在各个数据页上随机进行。从磁盘读取一个数据页后,该数据页上的所有行都被用来更新统计信息。统计信息更新的频率取决于字段或索引中的数据量以及数据更改量。比如,对于有一万条记录的表,当1000个索引键值发生改变时,该表的统计信息便可能需要更新,因为1000 个值在该表中占了10%,这是一个很大的比例。而对于有1千万条记录的表来说,1000个索引值发生更改的意义则可以忽略不计,因此统计信息就不会自动更新。至于它们帮助SQLS建立查询计划的具体过程,限于篇幅,这里就省略了,请有兴趣的朋友们自己研究。

            顺便多说一句,SQLS除了能自动记录统计信息之外,还可以记录服务器中所发生的其它活动的详细信息,包括I/O 统计信息、CPU 统计信息、锁定请求、T-SQL 和 RPC 统计信息、索引和表扫描、警告和引发的错误、数据库对象的创建/除去、连接/断开、存储过程操作、游标操作等等。这些信息的读取、设置请朋友们在SQLS联机帮助文档(SQL Server Books Online)中搜索字符串“Profiler”查找。

    索引的人工维护

            上面讲到,某些不合适的索引将影响到SQLS的性能,随着应用系统的运行,数据不断地发生变化,当数据变化达到某一个程度时将会影响到索引的使用。这时需要用户自己来维护索引。随着数据行的插入、删除和数据页的分裂,有些索引页可能只包含几页数据,另外应用在执行大量I/O的时候,重建非聚聚集索引可以维护I/O的效率。重建索引实质上是重新组织B树。需要重建索引的情况有:

    • 数据和使用模式大幅度变化;
    • 排序的顺序发生改变;
    • 要进行大量插入操作或已经完成;
    • 使用I/O查询的磁盘读次数比预料的要多;
    • 由于大量数据修改,使得数据页和索引页没有充分使用而导致空间的使用超出估算;
    • dbcc检查出索引有问题。

    索引的使用原则

            接近尾声的时候,让我们再从另一个角度认识索引的两个重要属性----唯一性索引和复合性索引。在设计表的时候,可以对字段值进行某些限制,比如可以对字段进行主键约束或唯一性约束。

            主键约束是指定某个或多个字段不允许重复,用于防止表中出现两条完全相同的记录,这样的字段称为主键,每张表都可以建立并且只能建立一个主键,构成主键的字段不允许空值。例如职员表中“身份证号”字段或成绩表中“学号、课程编号”字段组合。而唯一性约束与主键约束类似,区别只在于构成唯一性约束的字段允许出现空值

            建立在主键约束和唯一性约束上的索引,由于其字段值具有唯一性,于是我们将这种索引叫做“唯一性索引”,如果这个唯一性索引是由两个以上字段的组合建立的,那么它又叫“复合性索引”。

            注意,唯一索引不是聚集索引,如果对一个字段建立了唯一索引,你仅仅不能向这个字段输入重复的值。并不妨碍你可以对其它类型的字段也建立一个唯一性索引,它们可以是聚集的,也可以是非聚集的。

            唯一性索引保证在索引列中的全部数据是唯一的,不会包含冗余数据。如果表中已经有一个主键约束或者唯一性约束,那么当创建表或者修改表时,SQLS自动创建一个唯一性索引。但出于必须保证唯一性,那么应该创建主键约束或者唯一性键约束,而不是创建一个唯一性索引。当创建唯一性索引时,应该认真考虑这些规则:当在表中创建主键约束或者唯一性键约束时, SQLS将自动创建一个唯一性索引;如果表中已经包含有数据,那么当创建索引时,SQLS检查表中已有数据的冗余性,如果发现冗余值,那么SQLS就取消该语句的执行,并且返回一个错误消息,确保表中的每一行数据都有一个唯一值。

            复合索引就是一个索引创建在两个列或者多个列上。在搜索时,当两个或者多个列作为一个关键值时,最好在这些列上创建复合索引。当创建复合索引时,应该考虑这些规则:最多可以把16个列合并成一个单独的复合索引,构成复合索引的列的总长度不能超过900字节,也就是说复合列的长度不能太长;在复合索引中,所有的列必须来自同一个表中,不能跨表建立复合列;在复合索引中,列的排列顺序是非常重要的,原则上,应该首先定义最唯一的列,例如在(COL1,COL2)上的索引与在(COL2,COL1)上的索引是不相同的,因为两个索引的列的顺序不同;为了使查询优化器使用复合索引,查询语句中的WHERE子句必须参考复合索引中第一个列;当表中有多个关键列时,复合索引是非常有用的;使用复合索引可以提高查询性能,减少在一个表中所创建的索引数量。

            综上所述,我们总结了如下索引使用原则:

    • 逻辑主键使用唯一的成组索引,对系统键(作为存储过程)采用唯一的非成组索引,对任何外键列采用非成组索引。考虑数据库的空间有多大,表如何进行访问,还有这些访问是否主要用作读写。
    • 不要索引memo/note 字段,不要索引大型字段(有很多字符),这样作会让索引占用太多的存储空间。
    • 不要索引常用的小型表。
    • 一般不要为小型数据表设置过多的索引,假如它们经常有插入和删除操作就更别这样做了,SQLS对这些插入和删除操作提供的索引维护可能比扫描表空间消耗更多的时间。

    结尾

            查询是一个物理过程,表面上是SQLS在东跑西跑,其实真正大部分压马路的工作是由磁盘输入输出系统(I/O)完成,全表扫描需要从磁盘上读表的每一个数据页,如果有索引指向数据值,则I/O读几次磁盘就可以了。但是,在随时发生的增、删、改操作中,索引的存在会大大增加工作量,因此,合理的索引设计是建立在对各种查询的分析和预测上的,只有正确地使索引与程序结合起来,才能产生最佳的优化方案。

            一般来说建立索引的思路是:

    • 主键时常作为where子句的条件,应在表的主键列上建立聚聚集索引,尤其当经常用它作为连接的时候。
    • 有大量重复值且经常有范围查询和排序、分组发生的列,或者非常频繁地被访问的列,可考虑建立聚聚集索引。
    • 经常同时存取多列,且每列都含有重复值可考虑建立复合索引来覆盖一个或一组查询,并把查询引用最频繁的列作为前导列,如果可能尽量使关键查询形成覆盖查询。
    • 如果知道索引键的所有值都是唯一的,那么确保把索引定义成唯一索引。 
    • 在一个经常做插入操作的表上建索引时,使用fillfactor(填充因子)来减少页分裂,同时提高并发度降低死锁的发生。如果在只读表上建索引,则可以把fillfactor置为100。
    • 在选择索引字段时,尽量选择那些小数据类型的字段作为索引键,以使每个索引页能够容纳尽可能多的索引键和指针,通过这种方式,可使一个查询必须遍历的索引页面降到最小。此外,尽可能地使用整数为键值,因为它能够提供比任何数据类型都快的访问速度。
            SQLS是一个很复杂的系统,让索引以及查询背后的东西真相大白,可以帮助我们更为深刻的了解我们的系统。一句话,索引就象盐,少则无味多则咸。(深刻)
             
            借鉴博客及参考链接:


    展开全文
  • 索引原理和实现的方式

    千次阅读 2019-07-31 11:37:25
    建立索引 需要有索引的类型和索引的方法。 索引的类型包括了Normal Unique Full text 而索引的方法包括了BTREE 和HASH 转载文章:https://www.cnblogs.com/aspwebchh/p/6652855.html 博主写的真好~ 一、什么是...

    建立索引 需要有索引的类型和索引的方法。

    索引的类型包括了Normal  Unique Full text

    而索引的方法包括了BTREE 和HASH

    转载文章:https://www.cnblogs.com/aspwebchh/p/6652855.html  博主写的真好~

     

    一、什么是Btree B+ tree (非二叉)

    了解一个小知识:RDBMS即关系数据库管理系统(Relational Database Management System)

    1.avl树

    平衡二叉树,特点任何节点的两个子树的最大高度差为1

    如果进行插入或者删除操作,可能会导致avl树失去平衡,需要进行左旋右旋

    2.红黑树(自平衡二叉查找树)(需要知道红黑树和TreeMap排序原理)

    学习地址:https://www.cnblogs.com/CarpenterLee/p/5503882.html

    需要进行细化

     红黑树和avl树类似,都是在进行插入和删除操作时通过特定的操作保持二叉树的平衡,从而获得较高的查找性能。(如果平衡二叉树的话有着较高的性能优势

    特点:

            1.节点是红色或黑色

            2.根节点是黑色

            3.叶子节点(nil,空节点)是黑色

            4.每个红色节点的两个子节点都是黑色
    3 B-tree

    B通常理解成是Balance的意思,B- tree 就是B树,简称平衡树。

            B树是平衡多路查找树(有多个查找路径,不止2个),是一种平衡的多叉树。因为B树是平衡树,每个节点到叶子节点的高度都是相同的,这样可以保证B树的查询是稳定的。

            使用B tree 可以显著减少定位记录时所经历的中间过程,从而快速定位,加快存取速度。(建立索引快的一个原因)

            与二叉树相比,B-tree利用多个分支(二叉树只有2个分支)节点,减少了获取记录时所经历的节点数,从而达到节省存取时间的目的。(多个分支所经历的节点数少)

    特点:

            1.每个节点的关键字增多了。

            数据库充分利用了磁盘块的原理(磁盘数据的存储采用的是块的形式进行存储,每个块的大小一般为4k,每次去取数据的时候,就是取出这个4k的大小,而不是只取出你想要的大小。就是说每次IO的时候,同一磁盘块的数据都是一次性提取出来)。把树的节点关键字增多后,树的层级比原来二叉树的层级少了,这样就可以减少数据查找的次数 ,降低复杂度了。

    (磁盘的数据是成块的存放的)

            2.所有的叶子节点都在同一层上(要么就没有叶子节点要么都在同一层中)

    4.B+ tree

    B- tree的结构中,每个节点不仅包括数据的key值,也包括data值。而每一页的存储空间都是有限的,如果data数据较大的时候,会导致,每一页中存储的key比较少,当存储的数据量比较大时,同样会导致B- tree的查询深度很大,增加磁盘IO次数,进而影响查询效率

     B+ tree中,非叶子节点上只存储key的信息,这样可以加大每一页中存储key的数量,降低B+ tree的高度。

       1.非叶子节点只存储key信息

        2.所有叶子节点之间有一个链指针

        3.B+的非叶子节点只进行数据的索引不会存实际的关键字记录的指针所有数据地址必须要到叶子节点才能获取到,所以每次数据查询的次数都一样。

     

    二、索引的原理

    一个没加主键的表,它的数据无序的放置在磁盘存储器上,一行一行的排列的很整齐, 跟我认知中的「表」很接近。如果给表上了主键,那么表在磁盘上的存储结构就由整齐排列的结构转变成了树状结构,也就是上面说的「平衡树」结构,换句话说,就是整个表就变成了一个索引。

    这就是所谓的聚集索引,所以一个表只能有一个主键,一个表只能有一个聚集索引

    一个表只能有一个主键的~!!至于平时多个主键的原因是因为联合主键!!!(多个字段一起作为一张表的主键)

    主键的主键的作用是保证数据的唯一性和完整性,同时通过主键检索表能够增加检索速度。

    转自:https://www.cnblogs.com/aspwebchh/p/6652855.html

    因为主键的作用就是把「表」的数据格式转换成「索引(平衡树)」的格式放置。

    下面聊聊怎么创建联合主键:

    1、GUI中同时选中多列,点击设置为主键。(比如平时用的navicat)

    2、sql语句将多列设置为主键:

     一种是在建表时就写出,语句如下:

    Create Table 表名 (字段名1 Int Not Null,
                       字段名2 nvarchar(13) Not Null Primary Key (字段名1, 字段名2),
                        字段名3…………
                        字段名N………… )
    另一种是在建表后更改,语句如下:

    ALTER TABLE 表名 WITH NOCHECK ADD 
    CONSTRAINT [PK_表名] PRIMARY KEY  NONCLUSTERED 
    (
      [字段名1],
      [字段名2]
    )
    通过以上两种方式就解决了联合主键的问题。

    更多的公司选择id+type作为联合主键(暂时没有想到这样做的优势= =)

    clustered index 聚集索引,这类索引是在数据存在一起的。
    non-clustered 非聚集索引,这类索引是通过找聚集索引来找数据的。

    原文:https://blog.csdn.net/for12/article/details/49300843 

    假如一张表有一亿条数据 ,需要查找其中某一条数据,按照常规逻辑, 一条一条的去匹配的话, 最坏的情况下需要匹配一亿次才能得到结果,用大O标记法就是O(n)最坏时间复杂度,这是无法接受的,而且这一亿条数据显然不能一次性读入内存供程序使用, 因此, 这一亿次匹配在不经缓存优化的情况下就是一亿次IO开销,以现在磁盘的IO能力和CPU的运算能力, 有可能需要几个月才能得出结果 。如果把这张表转换成平衡树结构(一棵非常茂盛和节点非常多的树),假设这棵树有10层,那么只需要10次IO开销就能查找到所需要的数据, 速度以指数级别提升,用大O标记法就是O(log n),n是记录总树,底数是树的分叉数,结果就是树的层次数。换言之,查找次数是以树的分叉数为底,记录总数的对数,用公式来表示就是

    索引的缺点:

    查询数据的速度上升, 写入数据的速度下降,原因很简单的, 因为平衡树这个结构必须一直维持在一个正确的状态, 增删改数据都会改变平衡树各节点中的索引数据内容,破坏树结构, 因此,在每次数据改变时, DBMS必须去重新梳理树(索引)的结构以确保它的正确,这会带来不小的性能开销

    三:非聚集索引

    非聚集索引, 也就是我们平时经常提起和使用的常规索引。

    非聚集索引和聚集索引一样, 同样是采用平衡树作为索引的数据结构。索引树结构中各节点的值来自于表中的索引字段, 假如给user表的name字段加上索引 , 那么索引就是由name字段中的值构成,在数据改变时, DBMS需要一直维护索引结构的正确性。如果给表中多个字段加上索引 , 那么就会出现多个独立的索引结构,每个索引(非聚集索引)互相之间不存在关联。 如下图

    每次给字段建一个新索引, 字段中的数据就会被复制一份出来, 用于生成索引。 因此, 给表添加索引,会增加表的体积, 占用磁盘存储空间。

    非聚集索引和聚集索引的区别在于, 通过聚集索引可以查到需要查找的数据, 而通过非聚集索引可以查到记录对应的主键值 , 再使用主键的值通过聚集索引查找到需要的数据,如下图)

    不管以任何方式查询表, 最终都会利用主键通过聚集索引来定位到数据, 聚集索引(主键)是通往真实数据所在的唯一路径

    (也就是最后无论如何都要通过主键进行找到数据的)

    然而, 有一种例外可以不使用聚集索引就能查询出所需要的数据, 这种方法 称之为「覆盖索引」查询, 也就是平时所说的复合索引或者多字段索引查询。 文章上面的内容已经指出, 当为字段建立索引以后, 字段中的内容会被同步到索引之中, 如果为一个索引指定两个字段, 那么这个两个字段的内容都会被同步至索引之中。

     

    执行过程:

    使用CREATE 语句创建索引
    
    CREATE INDEX index_name ON table_name(column_name,column_name) include(score)
    

    //建立索引

    create index index_birthday on user_info(birthday);

    //查询生日在1991年11月1日出生用户的用户名

    select user_name from user_info where birthday = '1991-11-1'

    这句SQL语句的执行过程如下

    1.首先,通过非聚集索引index_birthday查找birthday等于1991-11-1的所有记录的主键ID值

    2.然后,通过得到的主键ID值执行聚集索引查找,找到主键ID值对就是真实数据(数据行)存储的位置

    最后, 从得到的真实数据中取得user_name字段的值返回, 也就是取得最终的结果

    我们把birthday字段上的索引改成双字段的覆盖索引

    create index index_birthday_and_user_name on user_info(birthday, user_name);

    这句SQL语句的执行过程就会变为

    通过非聚集索引index_birthday_and_user_name查找birthday等于1991-11-1的叶节点的内容,然而, 叶节点中除了有user_name表主键ID的值以外, user_name字段的值也在里面, 因此不需要通过主键ID值的查找数据行的真实所在, 直接取得叶节点中user_name的值返回即可。 通过这种覆盖索引直接查找的方式, 可以省略不使用覆盖索引查找的后面两个步骤, 大大的提高了查询性能

     

     

     

     

    展开全文
  • 聚集索引,非聚集索引,覆盖索引 原理

    万次阅读 多人点赞 2019-01-16 11:26:35
    「数据库」和「数据库索引」这两个东西是在服务器端开发领域...然而, 会使用索引是一回事, 而深入理解索引原理又能恰到好处使用索引又是另一回事,这完全是两个天差地别的境界(我自己也还没有达到这层境界)。...

    数据库」和「数据库索引」这两个东西是在服务器端开发领域应用最为广泛的两个概念,熟练使用数据库和数据库索引是开发人员在行业内生存的必备技能

    使用索引很简单,只要能写创建表的语句,就肯定能写创建索引的语句,要知道这个世界上是不存在不会创建表的服务器端程序员的。然而, 会使用索引是一回事, 而深入理解索引原理又能恰到好处使用索引又是另一回事,这完全是两个天差地别的境界(我自己也还没有达到这层境界)。很大一部份程序员对索引的了解仅限于到“加索引能使查询变快”这个概念为止。

    • 为什么要给表加上主键?
    • 为什么加索引后会使查询变快?
    • 为什么加索引后会使写入、修改、删除变慢?
    • 什么情况下要同时在两个字段上建索引?

    这些问题他们可能不一定能说出答案。知道这些问题的答案有什么好处呢?如果开发的应用使用的数据库表中只有1万条数据,那么了解与不了解真的没有差别, 然而, 如果开发的应用有几百上千万甚至亿级别的数据,那么不深入了解索引的原理, 写出来程序就根本跑不动,就好比如果给货车装个轿车的引擎,这货车还能拉的动货吗?

    接下来就讲解一下上面提出的几个问题,希望对阅读者有帮助。

    网上很多讲解索引的文章对索引的描述是这样的「索引就像书的目录, 通过书的目录就准确的定位到了书籍具体的内容」,这句话描述的非常正确, 但就像脱了裤子放屁,说了跟没说一样,通过目录查找书的内容自然是要比一页一页的翻书找来的快,同样使用的索引的人难到会不知道,通过索引定位到数据比直接一条一条的查询来的快,不然他们为什么要建索引。

    想要理解索引原理必须清楚一种数据结构「平衡树」(非二叉),也就是b tree或者 b+ tree,重要的事情说三遍:“平衡树,平衡树,平衡树”。当然, 有的数据库也使用哈希桶作用索引的数据结构 , 然而, 主流的RDBMS都是把平衡树当做数据表默认的索引数据结构的。

    我们平时建表的时候都会为表加上主键, 在某些关系数据库中, 如果建表时不指定主键,数据库会拒绝建表的语句执行。 事实上, 一个加了主键的表,并不能被称之为「表」。一个没加主键的表,它的数据无序的放置在磁盘存储器上,一行一行的排列的很整齐, 跟我认知中的「表」很接近。如果给表上了主键,那么表在磁盘上的存储结构就由整齐排列的结构转变成了树状结构,也就是上面说的「平衡树」结构,换句话说,就是整个表就变成了一个索引。没错, 再说一遍, 整个表变成了一个索引,也就是所谓的「聚集索引」。

    这就是为什么一个表只能有一个主键, 一个表只能有一个「聚集索引」,因为主键的作用就是把「表」的数据格式转换成「索引(平衡树)」的格式放置。

    在这里插入图片描述

    上图就是带有主键的表(聚集索引)的结构图。图画的不是很好, 将就着看。其中树的所有结点(底部除外)的数据都是由主键字段中的数据构成,也就是通常我们指定主键的id字段。最下面部分是真正表中的数据。 假如我们执行一个SQL语句:

    select * from table where id = 1256;
    

    首先根据索引定位到1256这个值所在的叶结点,然后再通过叶结点取到id等于1256的数据行。 这里不讲解平衡树的运行细节, 但是从上图能看出,树一共有三层, 从根节点至叶节点只需要经过三次查找就能得到结果。如下图

    在这里插入图片描述

    假如一张表有一亿条数据 ,需要查找其中某一条数据,按照常规逻辑, 一条一条的去匹配的话, 最坏的情况下需要匹配一亿次才能得到结果,用大O标记法就是O(n)最坏时间复杂度,这是无法接受的,而且这一亿条数据显然不能一次性读入内存供程序使用, 因此, 这一亿次匹配在不经缓存优化的情况下就是一亿次IO开销,以现在磁盘的IO能力和CPU的运算能力, 有可能需要几个月才能得出结果 。

    如果把这张表转换成平衡树结构(一棵非常茂盛和节点非常多的树),假设这棵树有10层,那么只需要10次IO开销就能查找到所需要的数据, 速度以指数级别提升,用大O标记法就是O(log n),n是记录总树,底数是树的分叉数,结果就是树的层次数。换言之,查找次数是以树的分叉数为底,记录总数的对数,用公式来表示就是

    在这里插入图片描述

    用程序来表示就是Math.Log(100000000,10),100000000是记录数,10是树的分叉数(真实环境下分叉数远不止10), 结果就是查找次数,这里的结果从亿降到了个位数。因此,利用索引会使数据库查询有惊人的性能提升。

    然而, 事物都是有两面的, 索引能让数据库查询数据的速度上升, 而使写入数据的速度下降,原因很简单的, 因为平衡树这个结构必须一直维持在一个正确的状态, 增删改数据都会改变平衡树各节点中的索引数据内容,破坏树结构, 因此,在每次数据改变时, DBMS必须去重新梳理树(索引)的结构以确保它的正确,这会带来不小的性能开销,也就是为什么索引会给查询以外的操作带来副作用的原因。

    讲完聚集索引 , 接下来聊一下非聚集索引, 也就是我们平时经常提起和使用的常规索引。

    非聚集索引和聚集索引一样, 同样是采用平衡树作为索引的数据结构。索引树结构中各节点的值来自于表中的索引字段, 假如给user表的name字段加上索引 , 那么索引就是由name字段中的值构成,在数据改变时, DBMS需要一直维护索引结构的正确性。如果给表中多个字段加上索引 , 那么就会出现多个独立的索引结构,每个索引(非聚集索引)互相之间不存在关联。 如下图

    在这里插入图片描述

    每次给字段建一个新索引, 字段中的数据就会被复制一份出来, 用于生成索引。 因此, 给表添加索引,会增加表的体积, 占用磁盘存储空间。

    非聚集索引和聚集索引的区别在于, 通过聚集索引可以查到需要查找的数据, 而通过非聚集索引可以查到记录对应的主键值 , 再使用主键的值通过聚集索引查找到需要的数据,如下图

    在这里插入图片描述

    不管以任何方式查询表, 最终都会利用主键通过聚集索引来定位到数据, 聚集索引(主键)是通往真实数据所在的唯一路径。

    然而, 有一种例外可以不使用聚集索引就能查询出所需要的数据, 这种非主流的方法 称之为「覆盖索引」查询, 也就是平时所说的复合索引或者多字段索引查询。 文章上面的内容已经指出, 当为字段建立索引以后, 字段中的内容会被同步到索引之中, 如果为一个索引指定两个字段, 那么这个两个字段的内容都会被同步至索引之中。

    先看下面这个SQL语句

    //建立索引
    
    create index index_birthday on user_info(birthday);
    
    //查询生日在1991年11月1日出生用户的用户名
    
    select user_name from user_info where birthday = '1991-11-1'
    
    

    这句SQL语句的执行过程如下:

    • 首先,通过非聚集索引index_birthday查找birthday等于1991-11-1的所有记录的主键ID值

    • 然后,通过得到的主键ID值执行聚集索引查找,找到主键ID值对就的真实数据(数据行)存储的位置

    • 最后, 从得到的真实数据中取得user_name字段的值返回, 也就是取得最终的结果

    我们把birthday字段上的索引改成双字段的覆盖索引

    create index index_birthday_and_user_name on user_info(birthday, user_name);
    

    这句SQL语句的执行过程就会变为:

    通过非聚集索引index_birthday_and_user_name查找birthday等于1991-11-1的叶节点的内容,然而, 叶节点中除了有user_name表主键ID的值以外, user_name字段的值也在里面, 因此不需要通过主键ID值的查找数据行的真实所在, 直接取得叶节点中user_name的值返回即可。 通过这种覆盖索引直接查找的方式, 可以省略不使用覆盖索引查找的后面两个步骤, 大大的提高了查询性能,如下图

    在这里插入图片描述

    数据库索引的大致工作原理就是像文中所述, 然而细节方面可能会略有偏差,这但并不会对概念阐述的结果产生影响 。

    展开全文
  • 索引的实现原理

    万次阅读 多人点赞 2017-09-17 21:48:37
    这篇文章是介绍MySQL数据库中的索引是如何根据需求一步步演变最终成为B+树结构的以及针对B+树索引的查询,插入,删除,更新等操作的处理方法。Oracle和DB2数据库索引的实现基本上也是大同小异的。文章写得很通俗易懂...
  • 索引原理

    千次阅读 2019-06-12 07:51:50
    有兴趣的同学可以看原文~ 使用索引很简单,只要能写创建表的语句,就肯定...然而, 会使用索引是一回事, 而深入理解索引原理又能恰到好处使用索引又是另一回事,这完全是两个天差地别的境界(我自己也还没有达到...
  • 了解数据库索引及其原理

    万次阅读 多人点赞 2018-06-25 16:10:30
    索引这个词相信对于一个开发猿来说,就好比看到我们的代码一样低头不见抬头见,...然而, 会使用索引是一回事, 而深入理解索引原理又能恰到好处使用索引又是另一回事,这完全是两个天差地别的境界(我自己也还没有...
  • 数据库索引原理 b树

    千次阅读 2018-05-17 09:49:38
    1 .B-树定义B-树是一种平衡的多路查找树,它在文件系统中很有用。定义:一棵m 阶的B-树,或者为空树,或为满足下列特性的m 叉树:⑴树中每个结点至多有m 棵子树;⑵若根结点不是叶子结点,则至少有两棵子树;...
  • MySQL索引实现原理分析

    万次阅读 多人点赞 2019-01-09 13:50:07
    目前大部分数据库系统及文件系统都采用B-Tree(B树)或其变种B+Tree(B+树)作为索引结构。B+Tree是数据库系统实现索引的首选数据结构。在MySQL中,索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的,...
  • 全文索引原理介绍

    万次阅读 2016-05-11 18:07:12
    一、总论 ... Lucene 是一个高效的,基于Java 的全文检索库。 ...所以在了解Lucene之前要费一番工夫了解一下全文检索。...我们生活中的数据总体分为两种:结构化数据 和非结构化数据 。...结构化数据: 指具
  • 数据库索引的实现原理

    万次阅读 多人点赞 2012-05-03 17:27:00
    说白了,索引问题就是一个查找问题。。。 数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。 在数据之外,数据库系统还维护着...
  • mysql联合索引原理

    千次阅读 2018-08-14 15:44:34
    col1表示的是年龄,col2表示的是姓氏,col3表示的是名字。如下图:    非叶子节点是按照年龄排序的,叶子点根据年龄排序之后再根据姓氏和名字排序;解释了最左原则...
  • 在网络上看到资料了解到 mysql 的索引使用 B-tree 实现的,使用关键字来存储索引的列。 单列的索引是使用一个 B-tree 的方式实现的。 但是,网上的大多资料都没有提及,组合索引的具体实现。 我个人猜测...
  • 联合索引原理

    千次阅读 2019-08-07 08:37:25
    https://segmentfault.com/a/1190000010991930
  • Solr工作原理

    万次阅读 2018-07-13 15:06:53
    Solr简介Solr是一个独立的企业级搜索应用服务器,它对外提供类似于...要想知道solr的实现原理,首先得了解什么是全文检索,solr的索引创建过程和索引搜索过程。一、全文检索首先举个例子:比如现在有5个文档,我现在...
  • mysql order by 原理和优化

    万次阅读 2017-08-07 03:40:16
    原理 利用索引的有序性获取有序数据 利用内存/磁盘文件排序获取结果 1) 双路排序:是首先根据相应的条件取出相应的排序字段和可以直接定位行数据的行指针信息,然后在sort buffer 中进行排序。 2)单路排序:是一次性...
  • Oracle索引原理

    千次阅读 2012-12-17 22:47:15
    索引是一种允许直接访问数据表中某一数据行的树型结构,为了提高查询效率而引入,是一个独立于表的对象,可以存放在与表不同的表空间中。索引记录中存有索引关键字和指向表中数据的指针(地址)。对索引进行的I/O...
  • MySQL索引原理、失效情况

    万次阅读 2020-06-08 09:57:49
    声明:本文是小编在学习过程中,...1.2 主键索引和普通索引的区别 1.3 唯一索引vs普通索引 2 mysql索引优化 2.1 查看索引使用情况 2.2 mysql索引使用策略 2.3 mysql索引使用原则 1 mysql索引知识 1.1 ...
  • MySQL innoDB索引底层原理详解

    万次阅读 多人点赞 2016-09-05 11:37:39
    本文介绍MySQL的InnoDB索引相对底层原理相关知识,涉及到B+Tree索引和Hash索引,但本文主要介绍B+Tree索引,其中包括聚簇索引和非聚簇索引,InnoDB数据页结构详解,B+Tree索引的使用以及优化,同时还有B+Tree索引的...
  • 总结自RunningSnail的博客https://www.cnblogs.com/tgycoder/p/5410057.html
1 2 3 4 5 ... 20
收藏数 305,172
精华内容 122,068
关键字:

索引原理