数据库索引如何工作?[关闭]


2418

鉴于索引随着数据集的增加而变得非常重要,有人可以解释索引在数据库不可知的级别是如何工作的吗?

有关查询索引字段的信息,请查看如何索引数据库列

Answers:


3546

为什么需要它?

当数据存储在基于磁盘的存储设备上时,它将作为数据块存储。完全访问这些块,使它们成为原子磁盘访问操作。磁盘块的结构与链接列表几乎相同。两者都包含一个数据节,一个指向下一个节点(或块)位置的指针,并且都不需要连续存储。

由于许多记录只能在一个字段上排序,因此我们可以说,在未排序的字段上进行搜索需要进行线性搜索,而线性搜索则需要N/2块访问(平均),其中N块的数量是表跨度。如果该字段是非关键字段(即不包含唯一条目),则必须在N块访问时搜索整个表空间。

而对于已排序的字段,可以使用具有log2 N块访问权限的二进制搜索。同样,由于给定的非关键字段对数据进行了排序,因此一旦找到更高的值,就不需要在表的其余部分中搜索重复的值。因此,性能的提高是可观的。

什么是索引?

索引是对多个字段上的多个记录进行排序的一种方式。在表中的字段上创建索引会创建另一个数据结构,该数据结构保存该字段值以及指向与其相关的记录的指针。然后对该索引结构进行排序,从而允许对其执行二进制搜索。

索引的不利之处在于,这些索引需要使用磁盘上的额外空间,因为使用MyISAM引擎将索引一起存储在一个表中,如果同一表中的许多字段都被索引了,则此文件可以快速达到基础文件系统的大小限制。

它是如何工作的?

首先,让我们概述一个示例数据库表架构;

字段名称数据类型磁盘大小
id(主键)无符号INT 4字节
firstName Char(50)50个字节
姓氏Char(50)50字节
emailAddress Char(100)100字节

注意:使用char代替varchar可以精确计算磁盘大小。该示例数据库包含五百万行,并且没有索引。现在将分析几个查询的性能。这些是使用id(已排序键字段)的查询,以及使用firstName(非键未排序字段)的查询。

实施例1 -排序VS未排序的字段

给定我们r = 5,000,000的固定大小记录的示例数据库,给出了记录长度的R = 204字节,并且使用MyISAM引擎将它们存储在表中,该引擎使用默认的块大小B = 1,024字节。该表的阻塞因子是bfr = (B/R) = 1024/204 = 5每个磁盘块的记录。保存该表所需的总块数为N = (r/bfr) = 5000000/5 = 1,000,000块。

N/2 = 500,000假设id字段是键字段,则对id字段进行线性搜索将需要平均块访问才能找到一个值。但是,由于id字段也已排序,因此可以进行二进制搜索,需要对log2 1000000 = 19.93 = 20块进行平均访问。立刻我们可以看到这是一个巨大的进步。

现在,firstName字段既没有排序,也没有关键字字段,因此二进制搜索是不可能的,值也不是唯一的,因此该表将需要搜索到末尾以进行精确的N = 1,000,000块访问。索引旨在纠正这种情况。

假定索引记录仅包含索引字段和指向原始记录的指针,则可以认为它会小于它指向的多字段记录。因此,索引本身比原始表需要更少的磁盘块,因此需要更少的块访问来进行迭代。下面概述了firstName字段上的索引的架构;

字段名称数据类型磁盘大小
firstName Char(50)50个字节
(记录指针)特殊的4个字节

注意:MySQL中的指针的长度为2、3、4或5个字节,具体取决于表的大小。

实施例2 -索引

给定我们的示例r = 5,000,000记录数据库,其中索引记录的长度为R = 54字节,并使用默认的块大小B = 1,024字节。索引的阻塞因子将是bfr = (B/R) = 1024/54 = 18每个磁盘块的记录。保持索引所需的总块数为N = (r/bfr) = 5000000/18 = 277,778块。

现在,使用firstName字段进行的搜索可以利用索引来提高性能。这允许使用log2 277778 = 18.08 = 19块访问的平均值对索引进行二进制搜索。要查找实际记录的地址,这需要进一步的块访问来读取,从而使总数进入19 + 1 = 20块访问,这与在非索引表中查找firstName匹配所需的1,000,000块访问相差甚远。

什么时候应该使用?

鉴于创建索引需要额外的磁盘空间(上例中增加了277,778个块,增加了约28%),并且索引过多可能会导致文件系统大小限制引起的问题,因此必须谨慎选择正确的磁盘空间。要索引的字段。

由于索引仅用于加速记录中匹配字段的搜索,因此可以推断出,仅用于输出的索引字段在执行插入或删除操作时只会浪费磁盘空间和处理时间,因此应该避免。同样考虑到二进制搜索的性质,数据的基数或唯一性也很重要。在基数为2的字段上建立索引会将数据分成两半,而基数为1,000的索引将返回大约1,000条记录。由于基数如此之低,有效性降低到了线性排序,并且如果基数小于记录数的30%,查询优化器将避免使用索引,有效地使索引浪费空间。


8
当数据唯一时可以进行二进制搜索,对吗?尽管您提到最小基数很重要,但是该算法不是简单的二进制搜索,这种近似值(〜log2 n)将如何影响处理时间?
洗发水

9
@AbhishekShivkumar:很好的问题!我认为索引表将具有与数据表中一样多的行。并且由于该字段只有2个值(布尔值为true / false),并说您想要一个值为true的记录,那么您只能将第一遍的结果集减半,在第二遍中,所有记录的值都为true,所以没有区别的基础,现在您必须以线性方式搜索数据表,因此他说在确定索引列时应考虑基数。在这种情况下,在这样的列上建立索引是毫无价值的。希望我是正确的:)
萨拉·帕蒂(Saurabh Patil)

7
一般情况下,块访问次数不应为(N+1)/2。如果我们将所有可能情况下的块访问次数相加,然后除以案例数,则N*(N+1)/(2*n)得出的结果就是(N+1)/2
2014年

31
我认为此答案有一些错别字,例如在句子中:“与非索引表所需的277,778个块访问区相去甚远。” 作者不是意味着要访问1,000,000个块?277,778是索引本身所需的块数。似乎也有其他一些错误:(
jcm 2014年

5
@jcm他在“什么是索引部分”中进行了解释-“索引是对多个字段上的多个记录进行排序的一种方法。在表中的字段上创建索引会创建另一个包含字段值和指针的数据结构到它所涉及的记录。然后对该索引结构进行排序,从而允许对其执行二进制搜索。”
grinch 2014年

292

经典示例“书籍索引”

考虑一本1000页的“书”,除以10章,每节100页。

简单吧?

现在,假设您想查找包含单词“ Alchemist ” 的特定章节。没有索引页,除了浏览整本书/章节之外,您别无选择。即:1000页。

这个类比在数据库世界中被称为“全表扫描”

在此处输入图片说明

但是有了索引页,您知道该去哪里!而且,要查找任何重要的特定章节,您只需一次又一次地浏览索引页面。找到匹配的索引后,您可以通过跳过其余部分来有效地跳到该章。

但是,除了实际的1000页之外,您还需要约10页来显示索引,因此总共需要1010页。

因此,索引是一个单独的部分,它以有效的查找顺序存储了索引列+指向索引行的指针的值。

在学校里事情很简单,不是吗?:P


23
真的很好比喻!有趣的是我没有在书索引和数据库索引之间建立联系
Yolo Voe '18

2
这让我思考Library还是Grocery Store 您能想象在杂货店没有索引吗? Where's The Beef?!? Oh its next to the Restrooms, a mop, and makeup
JayRizzo

3
“但是在开始时有一个索引页,您就在那里。” “你在那里”是什么意思?
Frisbetarian

2
索引通常放在书的后面,而目录放在前面。但是,这使类比更好,因为列顺序无关紧要。
解开

1
您的解释非常容易接受。其他人倾向于使用复杂的术语来解释事物。我希望我可以提出不止一项投票。
emeraldhieu

239

第一次阅读本文对我很有帮助。谢谢。

从那以后,我获得了有关创建索引的缺点的一些见解:如果您使用一个索引向一个表(UPDATEINSERT)中写入,则文件系统中实际上有两次写入操作。一个用于表数据,另一个用于索引数据(以及对它的替换(如果是群集的话,对表数据的替换))。如果表和索引位于同一硬盘上,则将花费更多时间。因此,没有索引的表(堆)将允许更快的写入操作。(如果您有两个索引,则最终将进行三个写操作,依此类推)

但是,在两个不同的硬盘上为索引数据和表数据定义两个不同的位置可以减少/消除时间成本增加的问题。这就需要使用所需的硬盘上的相应文件定义其他文件组,并根据需要定义表/索引位置。

索引的另一个问题是随着数据的插入它们随着时间的推移会碎片化。REORGANIZE帮助,您必须编写例程来完成它。

在某些情况下,堆比带有索引的表更有用,

例如:-如果您有许多相互竞争的文章,但在工作时间以外每晚只有一次阅读以作报告。

同样,区分聚簇索引和非聚簇索引也很重要。

帮助了我:- 聚集索引和非聚集索引的实际含义是什么?


3
我认为,可以通过维护两个不同的数据库来解决这些索引问题,就像Master和Slave一样。可以在其中使用Master来插入或更新记录。没有索引。而且slave可以用来以正确的索引进行读取???
bharatesh

14
不,错,对不起。不仅必须更新表的内容,还必须更新索引结构和内容(b树,节点)。您的主从概念在这里毫无意义。但是可行的做法是将其复制或镜像到对其进行分析的第二个数据库,以使该工作负载脱离第一个数据库。第二个数据库将保存数据副本该数据上的索引。
Der U

3
耶...!尝试阅读我的评论并正确理解它。我也说同样的话,我将主服务器和从服务器(无论如何)称为“复制或镜像到第二数据库,在该数据库上进行分析以减轻第一数据库的工作量。第二数据库将保存数据和索引的副本。该数据”
bharatesh 2014年

6
第二个数据库-完成镜像或复制的数据库,即从数据库-将像第一个数据库一样经历所有数据操作。对于每个dml操作,该第二个数据库上的索引将遇到“这些索引问题”。我看不到有什么好处,无论何时需要索引并建立索引以进行快速分析,它们都必须保持最新。
2014年

230

索引只是一种数据结构,它使搜索数据库中特定列的速度更快。该结构通常是b树或哈希表,但可以是任何其他逻辑结构。


28
+1百万乘以1的答案,因为我在尝试找到一个简单的解释实际上是什么索引的同时找到了此清单。
Josh Burson

1
让我们注意“仅数据结构”并不意味着“数据附加”。有时它是(例如“非聚集索引”),有时它确定数据的布局(例如“聚集索引”)。
Pablo H

159

现在,假设我们要运行一个查询来查找名为“ Abc”的所有员工的所有详细信息?

SELECT * FROM Employee 
WHERE Employee_Name = 'Abc'

没有索引会发生什么?

数据库软件实际上必须查看Employee表中的每一行,以查看该行的Employee_Name是否为'Abc'。而且,由于我们希望每行都包含名称为'Abc',因此,一旦找到名称为'Abc'的一行,我们就不能停止寻找,因为可能会有其他名称为Abc的行。因此,必须搜索直到最后一行的每一行-这意味着在这种情况下,数据库必须检查数千行才能找到名称为“ Abc”的行。这就是所谓的全表扫描

数据库索引如何帮助提高性能

拥有索引的全部目的是通过实质上减少表中需要检查的记录/行的数量来加快搜索查询的速度。索引是一种数据结构(最常见的是B树),用于存储表中特定列的值。

B树索引如何工作?

B树是索引最流行的数据结构的原因是它们具有时间效率的事实–因为查找,删除和插入都可以在对数时间内完成。而且,B树被更普遍使用的另一个主要原因是因为可以对存储在B树中的数据进行排序。RDBMS通常确定哪种数据结构实际用于索引。但是,在某些带有特定RDBMS的场景中,实际上可以指定在创建索引本身时希望数据库使用哪种数据结构。

哈希表索引如何工作?

使用哈希索引的原因是,哈希表在查找值时非常高效。因此,如果将查询与字符串进行相等性比较,则可以使用哈希索引快速检索值。

例如,我们前面讨论的查询可以从Employee_Name列上创建的哈希索引中受益。哈希索引的工作方式是,列值将成为哈希表中的键,而映射到该键的实际值将仅是指向表中行数据的指针。由于哈希表基本上是一个关联数组,因此典型的条目看起来像“ Abc => 0x28939”,其中0x28939是对表行的引用,该行存储有Abc。在哈希表索引中查找类似于“ Abc”的值并返回对内存中行的引用显然比扫描表在Employee_Name列中查找所有值为“ Abc”的行要快得多。

哈希索引的缺点

哈希表不是排序的数据结构,并且哈希查询甚至无法解决许多类型的查询。例如,假设您想找出所有不到40岁的员工。您如何使用哈希表索引来做到这一点?好吧,这是不可能的,因为哈希表仅适合于查找键值对–这意味着查询将检查是否相等

数据库索引内部到底是什么? 因此,现在您知道在表的列上创建了数据库索引,并且该索引将值存储在该特定列中。但是,重要的是要了解数据库索引不会将值存储在同一表的其他列中。例如,如果我们在Employee_Name列上创建索引,这意味着Employee_Age和Employee_Address列值也未存储在索引中。如果我们只是将所有其他列都存储在索引中,那么这就像在创建整个表的另一个副本一样–会占用太多空间并且效率很低。

数据库如何知道何时使用索引? 当运行“ SELECT * FROM Employee WHERE Employee_Name ='Abc'”之类的查询时,数据库将检查要查询的列上是否有索引。假设Employee_Name列上确实创建了索引,则数据库将不得不决定使用索引来查找正在搜索的值是否真正有意义–因为在某些情况下使用数据库索引的效率实际上较低,并且仅扫描整个表的效率更高。

数据库索引的成本是多少?

它占用空间–表越大,索引越大。索引对性能的另一个影响是,无论何时在相应表中添加,删除或更新行,都必须对索引执行相同的操作。请记住,索引需要包含与该索引涵盖的表列中相同的最新数据。

通常,只有经常查询索引列中的数据时,才应在表上创建索引。

也可以看看

  1. 哪些列通常可以构成良好的索引?
  2. 数据库索引如何工作

4
“数据库索引未将值存储在其他列中”-不正确。
mustaccio

2
@mustaccio:索引仅存储具有索引列的行的引用(据我所知)。我可能错了。您是否有任何引用说索引存储其他列值?
Somnath Muluk

3
@To Downvoters:您能解释一下哪里出了问题,以便我改善吗?
Somnath Muluk

2
检查例如SQL Server群集索引或DB2的CREATE INDEX ... INCLUDE子句。我认为您的答案有太多概括。
mustaccio

11
@mustaccio:因此默认情况下create index不包括其他列以及其原因。If we did just store all the other columns in the index, then it would be just like creating another copy of the entire table, which would take up way too much space and would be very inefficient.。这是索引的更通用的版本。CREATE INDEX ... INCLUDE是考虑其他专栏的较新版本。我已经解释的帖子正在考虑使用更通用的版本。如果我们考虑所有数据库,索引如何工作将是一本书?是不是 您认为答案值得否决吗?
Somnath Muluk

96

简单说明!

索引不过是一种数据结构,该数据结构存储表中特定列的值。在表的列上创建索引。

例如:我们有一个叫做数据库表User有三列- NameAgeAddress。假设该User表具有数千行。

现在,假设我们要运行一个查询来查找名为“ John”的所有用户的所有详细信息。如果我们运行以下查询:

SELECT * FROM User 
WHERE Name = 'John'

数据库软件实际上必须查看表中的每一行,User以查看Name该行的“是否”是“ John”。这将花费很长时间。

index对我们有帮助:索引用于通过实质上减少需要检查的表中的记录/行数来加速搜索查询

如何创建索引:

CREATE INDEX name_index
ON User (Name)

一个index一张表中列值(例如John)组成,这些值存储在数据结构中

因此,现在数据库将使用索引查找名为John的员工,因为该索引可能会按用户名的字母顺序进行排序。而且,由于它是经过排序的,这意味着搜索名称的速度要快得多,因为所有以“ J”开头的名称都将在索引中紧挨着!


1
索引并不意味着该列的排序顺序
oligofren

4
谢谢。这有助于我的理解。因此,基本上,索引是已排序的列数据的副本。通常,列数据按插入数据的顺序排列。
尼尔,

34

只是一个快速的建议。.由于索引会花费额外的写入和存储空间,因此,如果您的应用程序需要更多的插入/更新操作,则可能要使用不带索引的表,但是如果它需要更多的数据检索操作,则应该使用索引表。


6
这是评论,不是答案。
罗恩·约翰

5
由于它是一般性的注释,因此它更加可见,因此更有用。应该将此答案添加到哪个答案中?
pfabri

1
可能是关于OP的评论
Guyarad

33

只需将数据库索引视为一本书的索引即可。

如果您有一本关于狗的书,并且想查找有关例如德国牧羊犬的信息,那么您当然可以翻阅该书的所有页面并找到所需内容-但这当然是耗时的,而不是非常快。

另一个选择是,您可以转到书的“索引”部分,然后使用要查找的实体的名称(在本例中为“德国牧羊犬”)找到所需的内容,并查看要查找的页码。快速找到您想要的东西。

在数据库中,页码称为将数据库定向到实体所在磁盘上的地址的指针。使用相同的德国牧羊犬类比,我们可能会有类似的内容(“德国牧羊犬”,0x77129),其中0x77129是磁盘上存储德国牧羊犬行数据的地址。

简而言之,索引是一种数据结构,用于将表中特定列的值存储起来,从而加快查询搜索的速度。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.