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

How does database indexing work? [closed]

提问人:Xenph Yan 提问时间:8/4/2008 最后编辑:TRiGXenph Yan 更新时间:3/8/2022 访问量:1104241

问:


想改进这个问题吗?更新问题,使其仅通过编辑这篇文章来关注一个问题。

去年关闭。

社群在 9 个月前审查了是否重新打开这个问题,并关闭了这个问题:

原始关闭原因未解决

鉴于索引随着数据集大小的增加而变得如此重要,有人可以解释索引如何在与数据库无关的级别上工作吗?

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

SQL 性能 数据库 索引

评论


答:

4124赞 Xenph Yan 8/4/2008 #1

为什么需要它?

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

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

而对于排序字段,可以使用具有块访问的二叉搜索。此外,由于数据是在给定非键字段的情况下排序的,因此一旦找到更高的值,就不需要搜索表的其余部分以查找重复值。因此,性能的提高是可观的。log2 N

什么是索引?

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

索引的缺点是,这些索引需要额外的磁盘空间,因为索引是使用 MyISAM 引擎一起存储在表中的,如果对同一表中的许多字段进行索引,则此文件可以很快达到底层文件系统的大小限制。

它是如何工作的?

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

Field name       Data type      Size on disk
id (Primary key) Unsigned INT   4 bytes
firstName        Char(50)       50 bytes
lastName         Char(50)       50 bytes
emailAddress     Char(100)      100 bytes

注意:使用 char 代替 varchar 以允许准确的磁盘大小值。 此示例数据库包含 500 万行,并且未编制索引。现在将分析几个查询的性能。这些是使用 id(排序键字段)的查询和使用 firstName(非键未排序字段)的查询。

示例 1 - 已排序字段与未排序字段

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

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

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

假设索引记录仅包含索引字段和指向原始记录的指针,则按理说它将小于它所指向的多字段记录。因此,索引本身需要的磁盘块比原始表少,因此需要更少的块访问来循环访问。firstName 字段上的索引架构概述如下;

Field name       Data type      Size on disk
firstName        Char(50)       50 bytes
(record pointer) Special        4 bytes

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

示例 2 - 索引

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

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

什么时候应该使用?

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

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

评论

10赞 shampoo 2/3/2013
当数据是唯一的时,可以进行二进制搜索,对吗?虽然您提到最小基数很重要,但该算法不会是简单的二进制搜索,这种近似值 (~log2 n) 将如何影响处理时间?
15赞 Saurabh Patil 7/8/2013
@AbhishekShivkumar:问得好!我认为索引表的行数将与数据表中的行数一样多。由于这个字段只有 2 个值(带有 true/false 的布尔值)并且假设您想要一个值为 true 的记录,那么您只能在第一次传递中将结果集减半,在第二遍中,您的所有记录都具有值 true,因此没有区分的基础,现在您必须以线性方式搜索数据表——因此他说在决定索引列时应该考虑基数。在这种情况下,在这样的列上建立索引是没有价值的。希望我是对的:)
8赞 ajay 1/30/2014
在平均情况下,块访问次数不应该是 。如果我们将所有可能情况的块访问次数相加,然后除以病例数,那么我们得到的是 .(N+1)/2N*(N+1)/(2*n)(N+1)/2
38赞 jcm 8/24/2014
我认为这个答案中有一些错别字,例如,在句子中:“与非索引表所需的 277,778 个块访问相去甚远。 作者的意思不是 1,000,000 次块访问吗?277,778 是索引本身所需的块数。似乎还有其他一些不准确之处 :(
7赞 grinch 11/13/2014
@jcm 他在“什么是索引”部分对此进行了解释——“索引是一种对多个字段上的许多记录进行排序的方法。在表中的字段上创建索引会创建另一个数据结构,该结构包含字段值,以及指向与其相关的记录的指针。然后对这个索引结构进行排序,允许对其执行二进制搜索。
265赞 Der U 4/30/2013 #2

我第一次读到这篇文章时,它对我非常有帮助。谢谢。

从那时起,我对创建索引的缺点有了一定的了解: 如果写入具有一个索引的表 ( 或 ),则文件系统中实际上有两个写入操作。一个用于表数据,另一个用于索引数据(以及索引数据的排序(以及 - 如果聚类 - 表数据的排序))。如果表和索引位于同一硬盘上,则会花费更多时间。因此,没有索引(堆)的表将允许更快的写入操作。(如果你有两个索引,你最终会得到三个写入操作,依此类推)UPDATEINSERT

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

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

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

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

此外,区分聚簇索引和非聚簇索引也相当重要。

帮助了我:- 聚簇和非聚簇索引实际上是什么意思?

评论

3赞 bharatesh 5/23/2014
我认为,这些索引问题可以通过维护两个不同的数据库来解决,就像 Master 和 Slave 一样。其中 Master 可用于插入或更新记录。没有索引。并且 slave 可用于读取具有正确索引权限的正确索引???
14赞 Der U 5/30/2014
不,错了,对不起。不仅要更新表的内容,还要更新索引结构和内容(B-tree、节点)。你的主人和奴隶的概念在这里毫无意义。不过,可行的方法是复制或镜像到第二个数据库,在该数据库上进行分析,以将该工作负载从第一个数据库中移走。第二个数据库将保存数据副本该数据的索引。
3赞 bharatesh 6/2/2014
你。。。!尝试阅读我的评论并正确理解它。我也说过同样的话,我将主站和从站(无论什么)称为“复制或镜像到第二个数据库,在该数据库上进行分析以将工作负载从第一个数据库移走。第二个数据库将保存数据副本和该数据的索引”
6赞 Der U 6/3/2014
第二个数据库(完成镜像或复制的数据库,即从数据库)将像第一个数据库一样经历所有数据操作。对于每个 DML 操作,第二个数据库上的索引都会遇到“这些索引问题”。我看不出这有什么好处,无论哪里需要索引并为快速分析而构建,它们都需要保持最新状态。
348赞 hcarreras 2/20/2014 #3

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

评论

57赞 Josh Burson 6/23/2015
+1 乘以一百万的答案,因为我在试图找到一个简单的解释时发现了这个列表,索引的本质是什么。
5赞 Pablo H 8/28/2019
请注意,“只是一个数据结构”并不意味着“数据的附加”。有时它是(例如“非聚簇索引”),有时它决定了数据的布局(例如“聚簇索引”)。
3赞 Diego Ramos 3/26/2021
这是最好的答案,索引基本上就像一个哈希图,其中 get 具有 O(1) 的复杂度,而在 List 中搜索的复杂度为 O(N)
133赞 ProgrammerPanda 8/2/2016 #4

简单的描述!

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

示例:我们有一个数据库表,它有三列 – 和 。假设该表有数千行。UserNameAgeAddressUser

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

SELECT * FROM User 
WHERE Name = 'John'

从字面上看,数据库软件必须查看表中的每一行,以确定该行是否为“John”。这将需要很长时间。UserName

这就是帮助我们的地方:索引用于通过减少表中需要检查的记录/行数来加快搜索查询速度。index

如何创建索引:

CREATE INDEX name_index
ON User (Name)

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

因此,现在数据库将使用索引来查找名为 John 的员工 因为索引大概会按字母顺序排序 用户名。而且,因为它是排序的,所以这意味着搜索一个名字 速度要快得多,因为所有以“J”开头的名称都是正确的 在索引中彼此相邻!

评论

3赞 oligofren 2/15/2019
索引并不意味着列的排序顺序
12赞 Neil 5/1/2019
谢谢。这有助于我的理解。因此,基本上索引是已排序的列数据的副本。通常,列数据只是按照插入数据的顺序排列。
0赞 Dev1ce 10/27/2020
这是否意味着在内部,每个名称都维护一个单独的表,例如 Name=John 有自己的表
1赞 Jason S 11/13/2020
“索引只不过是一种数据结构,用于存储表中特定列的值”——为什么这么说?我认为价值还不够;相反,它必须在表中存储对行/记录的引用。如果我有一个包含 10 列的表,其中一列是 ,索引不能只存储 的值,它必须存储对表行的引用。否则,如果对另一列执行 SELECT,但 join/select on 将无法单独使用这些值。COUNTRY_CODECOUNTRY_CODECOUNTRY_CODECOUNTRY_CODE
200赞 Somnath Muluk 8/14/2016 #5

现在,假设我们想要运行一个查询来查找任何名为“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 8/14/2016
“数据库索引不存储其他列中的值” -- 不为 true。
3赞 Somnath Muluk 8/14/2016
@mustaccio:索引仅存储包含索引列的行的引用(据我所知)。我可能错了。你有没有参考资料说索引存储其他列值?
5赞 Somnath Muluk 8/14/2016
@To Downvoters : 你能解释一下问题所在,以便我改进吗?
16赞 Somnath Muluk 8/14/2016
@mustaccio:因此,默认情况下不包括其他列以及为什么应该包含其他列。.这是索引的更通用版本。 通过考虑其他列,是较新的版本。我解释过的帖子正在考虑更通用的版本。如果我们考虑所有数据库,索引是如何工作的,这将是一本书?不是吗?你认为答案值得投反对票吗?create indexIf 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
4赞 hcarreras 7/6/2017
这基本上是从 programmerinterview.com/index.php/database-sql/what-is-an-index 中提取的 也许你可以补充一下
42赞 Alf Moh 12/22/2016 #6

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

如果你有一本关于狗的书,你想找到关于德国牧羊犬的信息,你当然可以翻阅这本书的所有页面,找到你要找的东西——但这当然很耗时,而且不是很快。

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

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

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

625赞 Sankarganesh Eswaran 4/23/2017 #7

经典例子“书籍索引”

考虑一本 1000 页的“书”,分为 10 章,每节有 100 页。

很简单,对吧?

现在,想象一下你想找到一个包含“炼金术士”这个词的特定章节。如果没有索引页,您别无选择,只能浏览整本书/章节。即:1000 页。

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

enter image description here

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

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

因此,索引是一个单独的部分,用于存储索引值 列 + 指针按排序顺序指向索引行,以便高效 查找。

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

评论

92赞 Yolo Voe 7/12/2018
真是个好比喻!有趣的是,我没有在书籍索引和数据库索引之间建立联系
7赞 JayRizzo 9/4/2018
这让我想到或者你能想象在杂货店没有索引吗?LibraryGrocery StoreWhere's The Beef?!? Oh its next to the Restrooms, a mop, and makeup
5赞 Frisbetarian-Support Palestine 9/13/2018
“但是一开始有一个索引页,你就在那里。”“you are there” 是什么意思?
3赞 undrline - Reinstate Monica 7/9/2019
索引通常放在书的后面,而目录放在前面。但是,这使得类比更好,因为列顺序应该无关紧要。
0赞 D0mm 9/17/2021
我仍然不完全理解,所以如果有 n 个唯一的单词,索引对我有什么帮助?它为每个单词创建指针?如果是这样,找到该指针需要很多时间,甚至可能在同一时间,然后只需滚动所有内容并以默认方式找到它