跳转到内容

结构化查询语言/索引管理

来自维基教科书,开放世界中的开放书籍



索引是所有 SQL 数据库的关键特性。它们提供对数据的快速访问。因此,几乎所有实现都支持 CREATE INDEX 语句。

然而,CREATE INDEX 语句不是 SQL 标准的一部分!其原因尚不清楚。可能是刻意避免所有实现问题。或者,它源于供应商实现的各种语法和缺乏找到妥协方案。

在本页,我们提供了一些关于索引的基本概念以及大多数实现中通用的语法。

CREATE [UNIQUE] INDEX <index_name> ON <table_name> (<column_name> [, <column_name>]);

索引的概念

[编辑 | 编辑源代码]

DBMS 提供对存储在表中的数据的快速访问。人们可能会认为这种高速访问是由于现代计算机的快速硬件:每秒数百万个 CPU 周期,毫秒级的 I/O 速率,微秒或纳秒级的 RAM 访问等等。这是事实,但只是一部分。相反,智能软件算法的使用,特别是在处理大量数据时,是主要因素。

考虑向 DBMS 发出请求,以确定是否可以在包含 100 万个条目的表中找到一个特定姓名的人。使用原始的线性算法,系统必须读取平均 50 万行才能决定问题。该二分查找算法实现了一种更复杂的策略,该策略在读取 20 行或更少行后即可回答问题。在这种情况下,这种算法选择导致性能提升 25000 倍。为了真正理解这种改进的程度,您可能需要将您的薪水乘以 25000。

诚然,线性访问和二分查找算法之间的这种比较有点简单。首先,DBMS 通常读取包含多行而不是单行的块。但这并没有改变情况。如果一个块包含 100 行,将上面的例子从 100 万行修改为 1 亿行。其次,二分查找算法假设数据是有序的。这意味着在数据输入期间,需要额外的步骤将实际输入排序到现有数据中。这仅应用一次,并且与读取访问次数无关。总之,在数据输入期间有额外的工作,而在数据访问期间有更少的工作。这取决于数据的典型使用,以确定额外的工作是否值得。

索引是一个额外的存储,它保存从表中的原始数据复制或推导出的数据。它只包含冗余数据。索引包含哪些部分?在二分查找策略的常见情况下,索引保存表列的原始值以及指向原始行的反向引用。在大多数情况下,索引被组织成一个平衡树,其中列的值作为树的键,反向引用作为每个键的附加信息。

二分查找算法只是构建索引的众多选项之一。索引的共同特征是:它们仅包含冗余信息;在 CPU 周期、RAM 或磁盘空间方面使用额外的资源;并为大型数据集上的查询提供更好的性能。在小表或包含多个索引的表的情况下,索引的缺点(性能或资源使用方面)可能超过使用索引的好处。

基本索引

[编辑 | 编辑源代码]

如果一个应用程序根据某个条件检索数据 - 例如,电话簿应用程序中的个人姓名 - 并且该条件包含表的列,则该列应该有索引。

CREATE INDEX person_lastname_idx ON person(lastname);

索引有它自己的可自由选择的名称 - 例如person_lastname_idx - 并且是在特定表中的特定列上构建的。索引可以在 CREATE TABLE 语句之后直接定义和创建(当表中没有数据时)或者在一些或大量 INSERT 命令之后创建。创建之后,DBMS 应该处于能够比以前更快地回答以下问题的状态。

SELECT count(*) 
FROM   person
WHERE  lastname = 'Miller';

索引可以在评估 WHERE 子句期间使用。DBMS 可以选择:一方面 - 读取所有person行并计算 lastname 为 'Miller' 的行;另一方面 - 读取索引(可能使用二分查找)并计算所有值为 'Miller' 的节点。使用哪种策略取决于很多决定。例如,如果 DBMS 知道大约 30% 的行包含 'Miller',它可能会选择与知道只有 0.3% 的行包含 'Miller' 时不同的策略。

一个表可以有多个索引。

CREATE INDEX person_firstname_idx ON person(firstname);

在这种情况下,以下查询会发生什么?

SELECT count(*) 
FROM   person
WHERE  lastname = 'Miller'
AND    firstname = 'Henry';

同样,DBMS 有不止一种选择来检索预期的结果。它可以使用两个索引中的一个,读取结果行并查找缺少的另一个值。或者它读取两个索引并计算共同的反向引用。或者它忽略两个索引,读取数据并计算两个条件都适用的行。如前所述,这取决于很多决定。

多列索引

[编辑 | 编辑源代码]

如果一个应用程序通常在一个查询中搜索两列,例如姓名和姓氏,则为这两列构建一个索引可能很有用。这种策略与上面的例子有很大不同,在上面的例子中,我们为每列构建了两个独立的索引。

CREATE INDEX person_fullname_idx ON person(lastname, firstname);

在这种情况下,平衡树的键是姓氏和姓名的串联。DBMS 可以使用此索引来处理查询姓名和姓名的查询。它还可以使用此索引来处理仅查询姓氏的查询。但它不能在仅查询姓名的查询中使用此索引。姓名可以在平衡树的不同位置出现。因此,它对这种查询毫无用处。

函数索引

[编辑 | 编辑源代码]

在某些情况下,现有索引不能用于查询基础列。假设对姓名的查询应该是不区分大小写的。为此,应用程序将所有用户输入转换为大写并使用 UPPER() 函数来处理范围内的列。

-- Original user input was: 'miller'
SELECT count(*) 
FROM   person
WHERE  UPPER(lastname) = 'MILLER';

由于 WHERE 子句中的条件只查找大写字符,并且索引是以区分大小写的方式构建的,因此平衡树中的键毫无用处:'miller' 排序的位置与 'Miller' 大不相同。为了解决这个问题,可以定义一个索引,它使用与 WHERE 条件完全相同的策略。

CREATE INDEX person_uppername_idx ON person(UPPER(lastname)); -- not supported by MySQL

现在,'UPPER()' 查询可以使用这种所谓的函数索引。

唯一索引

[编辑 | 编辑源代码]

每个表的 主键都是唯一的,这意味着没有两行可以包含相同的值。有时,一列或几列的串联也是唯一的。要确保此条件,您可以定义一个唯一约束,或者您可以定义一个带有额外唯一条件的索引。(通常,唯一约束在后台默默地使用唯一索引。)

CREATE UNIQUE INDEX person_lastname_unique_idx ON person(lastname);

唯一索引只能在现有数据上创建,如果范围内的列确实只包含唯一的值(这在我们的数据库示例中并非如此)。

删除索引

[编辑 | 编辑源代码]

可以使用以下命令删除索引

DROP INDEX <index_name>;


华夏公益教科书