【问题标题】:How does database indexing work? [closed]数据库索引如何工作? [关闭]
【发布时间】:2010-09-05 07:00:59
【问题描述】:

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

有关查询字段索引的信息,请查看How do I index a database column

【问题讨论】:

    标签: sql database performance indexing database-indexes


    【解决方案1】:

    为什么需要它?

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

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

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

    什么是索引?

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

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

    它是如何工作的?

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

    字段名称 数据类型 磁盘大小 id (主键) 无符号 INT 4 字节 名字 Char(50) 50 字节 姓氏 Char(50) 50 字节 电子邮件地址 Char(100) 100 字节

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

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

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

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

    现在 firstName 字段既不是排序的也不是关键字段,因此不可能进行二分搜索,值也不是唯一的,因此该表将需要搜索到最后一个确切的 @987654332 @ 阻止访问。索引旨在纠正这种情况。

    鉴于索引记录只包含索引字段和指向原始记录的指针,按道理它会小于它所指向的多字段记录。所以索引本身比原始表需要更少的磁盘块,因此需要更少的块访问来迭代。 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 块访问,与在非索引表。

    什么时候应该使用?

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

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

    【讨论】:

    • 当数据唯一时可以进行二分查找,对吗?尽管您提到最小基数很重要,但该算法不会是简单的二进制搜索,这种近似值(~log2 n)将如何影响处理时间?
    • @AbhishekShivkumar:好问题!我认为索引表的行数与数据表中的行数一样多。并且由于该字段将只有 2 个值(布尔值,true/false)并说您想要一个值为 true 的记录,那么您只能在第一遍中将结果集减半,在第二遍中,您的所有记录的值都为 true,所以有没有区分的基础,现在您必须以线性方式搜索数据表 - 因此他说在确定索引列时应考虑基数。在这种情况下,对这样的列进行索引是毫无价值的。希望我是对的:)
    • 一般情况下的块访问次数不应该是(N+1)/2。如果我们将所有可能情况下的块访问次数相加,然后除以情况数,那么我们就有N*(N+1)/(2*n),即(N+1)/2
    • 我认为这个答案有一些错别字,例如,在句子中:“与非索引表所需的 277,778 块访问相去甚远。”作者的意思不是 1,000,000 块访问吗? 277,778 是索引本身所需的块数。似乎还有其他一些不准确之处:(
    • @jcm 他在“什么是索引部分”中解释了它-“索引是一种对多个字段上的多个记录进行排序的方法。在表中的字段上创建索引会创建另一个数据结构它保存字段值和指向它相关记录的指针。然后对该索引结构进行排序,允许对其执行二进制搜索。"
    【解决方案2】:

    经典示例“书籍索引”

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

    简单吧?

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

    这个类比在数据库领域被称为“全表扫描”

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

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

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

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

    【讨论】:

    • 非常好的类比!有趣的是,我没有在图书索引和数据库索引之间建立联系
    • 这让我想到LibraryGrocery Store 你能想象在杂货店没有索引吗? Where's The Beef?!? Oh its next to the Restrooms, a mop, and makeup
    • "但是开头有一个索引页,你就在那里。" “你在那里”是什么意思?
    • 索引通常放在书的后面,而目录放在前面。但是,这使得类比变得更好,因为列顺序无关紧要。
    • 我还是不太明白,所以如果有 n 个唯一词,索引对我有什么帮助?它为每个单词创建指针?如果是这样,甚至可能在同一时间找到该指针需要花费大量时间,然后只需滚动所有内容并以默认方式找到它
    【解决方案3】:

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

    【讨论】:

    • +1 次获得这个答案,因为我在试图找到索引本质上是什么的简单解释时发现了这个清单。
    • 请注意,“只是一个数据结构”并不意味着“附加到数据”。有时它是(例如“非聚集索引”),有时它决定了数据的布局(例如“聚集索引”)。
    • 这是最好的答案,Index 基本上就像一个 Hashmap,其中 get 的复杂度为 O(1),而在 List 中搜索的复杂度为 O(N)
    【解决方案4】:

    第一次读到这篇文章对我很有帮助。谢谢。

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

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

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

    在某些情况下,堆比有索引的表更有帮助,

    例如:- 如果您有很多相互竞争的写入,但在工作时间之外只有一个夜间阅读用于报告。

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

    帮助我:- What do Clustered and Non clustered index actually mean?

    【讨论】:

    • 我认为,这些索引问题可以通过维护两个不同的数据库来解决,就像Master和Slave一样。其中 Master 可用于插入或更新记录。没有索引。并且slave可以用来读取正确的索引权???
    • 不,错了,对不起。不仅表的内容必须更新,而且索引结构和内容(b-tree、节点)也必须更新。你的主人和奴隶的概念在这里没有意义。可行的方法是复制或镜像到第二个数据库,在该数据库上进行分析以从第一个数据库中移除该工作负载。第二个数据库将保存数据的副本该数据的索引。
    • 你...!尝试阅读我的评论并正确理解它。我也说过同样的话,我将主从(无论如何)称为“复制或镜像到第二个数据库,在该数据库上进行分析以将工作负载从第一个数据库中移除。第二个数据库将保存数据和索引的副本该数据”
    • 第二个数据库——对它进行镜像或复制的从属数据库——将像第一个数据库一样经历所有的数据操作。对于每个 dml 操作,第二个数据库上的索引都会遇到“这些索引问题”。我没有看到其中的好处,无论何时需要索引并为快速分析而构建它们都需要保持最新。
    【解决方案5】:

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

    SELECT * FROM Employee 
    WHERE Employee_Name = 'Abc'
    

    没有索引会怎样?

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

    数据库索引如何提高性能

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

    B-trees 索引如何工作?

    B-树是最流行的索引数据结构的原因在于它们具有时间效率——因为查找、删除和插入都可以在对数时间内完成。而且,B-tree 更常用的另一个主要原因是因为存储在 B-tree 中的数据可以排序。 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. What columns generally make good indexes?
    2. How do database indexes work

    【讨论】:

    • “数据库索引不存储其他列中的值”——不正确。
    • @mustaccio:索引仅存储带有索引列的行引用(据我所知)。我可能错了。你有没有说索引存储其他列值的参考资料?
    • @To Downvoters : 你能解释一下有什么问题让我改进吗?
    • 检查例如 SQL Server 集群索引或 DB2 的 CREATE INDEX ... INCLUDE 子句。在我看来,你的回答中有太多概括。
    • @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 是考虑其他列的较新版本。我已经解释过的帖子正在考虑更通用的版本。如果我们考虑所有数据库,索引如何工作将是一本书?不是吗?你认为答案值得否决吗?
    【解决方案6】:

    简单说明!

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

    示例:我们有一个名为 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”开头的名字都是正确的 在索引中彼此相邻!

    【讨论】:

    • 索引并不意味着列的排序顺序
    • 谢谢。这有助于我的理解。所以基本上索引是已排序的列数据的副本。通常,列数据只是按照数据插入的顺序。
    • 这是否意味着在内部,为每个名称维护一个单独的表,例如 Name=John 有它自己的表
    • “索引只不过是一种数据结构,用于存储表中特定列的值”——为什么这么说?我认为价值还不够;相反,它必须在表中存储对行/记录的引用。如果我有一个包含 10 列的表,其中之一是 COUNTRY_CODE,则索引不能只存储 COUNTRY_CODE 的值,它必须存储对表行的引用。否则,如果您对另一列执行 SELECT 但在 COUNTRY_CODE 上加入/选择,您将无法单独使用 COUNTRY_CODE 值。
    【解决方案7】:

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

    【讨论】:

    • 这是评论,不是答案。
    • 这种方式更明显,因此更有帮助,因为它是一般性的评论。应该将此作为评论添加到哪个答案中?
    • 可能是对 OP 的评论
    【解决方案8】:

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

    如果您有一本关于狗的书,并且想查找有关德国牧羊犬的信息,您当然可以翻阅这本书的所有页面并找到您要查找的内容 - 但现在当然是时候了消耗而且不是很快。

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-09-26
      • 2011-02-26
      • 2010-09-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多