跳至主要内容

SQL 索引入门

在本教程中,了解数据库中的索引以及不同类型的索引技术。
· 14分钟 阅读

使用 AI 探索

作为一名数据科学家,您经常需要处理海量数据。处理如此庞大的数据绝非易事。为了尽可能高效地处理它,您需要清楚了解数据在物理层面的组织方式,这样才能运用顺手的技术来处理。

SQL 是现代软件工程师必备的技能,因为大多数软件都依赖某种数据,并且能与 RDBMS(关系型数据库管理系统)很好地集成。无论是 Web 应用、API,还是内部应用,RDBMS 始终存在。而 SQL 则是查询 RDBMS 的语言。

对数据科学家来说,掌握 SQL 及其相关技术至关重要。为了能查询 RDBMS 并获取您对所处理数据的特定问题的答案,SQL 是最低限度的需求。

在他与 DataCamp 最新的视频中,David Robinson(DataCamp 首席数据科学家)向我们展示了他如何在一个数据科学问题中使用 SQL。请务必看看。他的工作流程非常有趣。

今天,您将学习一种名为索引的技术,它主要涉及数据库内部的数据组织方式,同时您还将使用 SQL 实现其中的一些方法。这将帮助您了解如何利用索引在数据库中存储信息,以及它如何带来更快的执行时间。

注意:在开始阅读本教程之前,强烈建议您先了解 SQL 基础知识(如果您还不熟悉)。DataCamp 的 数据科学 SQL 入门课程是复习 SQL 基础的优秀资源。

关于文件中记录组织的一点说明

在开始学习索引之前,了解数据在文件内部的物理组织方式至关重要。显然,您不会深入到所有细节,但对组织方式有一个良好的概览将有助于您清晰地理解索引。让我们开始吧。

记录/数据的一般组织方式涉及记录如何存储,大体可分为两类:

  • 有序组织:文件中的所有记录按某个搜索键值排序。通常使用二分查找在有序文件中搜索任何条目。这样搜索非常高效,因为时间复杂度保持在对数时间。但向文件中插入新记录会代价高昂,因为您可能需要重新组织整个文件来容纳新条目。

  • 无序组织:在这种类型中,记录会被插入到文件中的任意可用位置,通常是文件末尾。由于此类使用线性查找,搜索效率不如前者,但插入操作并不昂贵。

即便文件是有序组织的,如果文件的块大小以及每条记录的大小都非常大,会怎样?我们在下一节来探讨。

索引的必要性

本节将从使用索引以高效存储文件/信息的动机讲起,并说明它如何提升与这些文件/信息相关的其他操作。

设想在数据库中有一张包含多条记录的表(关系)。这些记录又被划分为 1000 个块。图示组织如下:

table blocks

从上面的组织可以看出:

  • 排序应用于记录的第一列(假设这是一个以表格形式组织数据的关系型数据库系统)
  • 总数据被划分的块数为:1000 个块。

请记住,每条记录还包含其他列,但在该组织中,排序应用于第一列,块据此划分。现在,如果您使用二分查找在这种记录组织中搜索内容,总耗时为 $\log_{2}1000$,也就是 10 个时间单位。这个搜索时间还能改进吗?

当然可以。

不妨把它类比为读书却没有索引页。您随机翻到一页(或者在二分查找中翻到书的中间),再根据需要向左或向右翻,直至找到目标页。这显然要花时间,对吧?

但如果书中有索引页,搜索会更高效。您可以通过查找索引直接翻到目标页。上述情境中同样可以应用索引思想。

以下是您在此处应用索引的概要:

  • 在保留前述有序值的同时,为每个块维护一个块指针。

这将使用于存储数据文件的块数量减少。现在,为了搜索某条特定记录,您只需在由较少块组成的新组织中搜索,最终就能以更少的时间找到目标记录(如果存在)。我们来把它可视化。假设新建的索引记录最终为 8 个块,组织将如下所示:

less table blocks

块数更少意味着搜索时间将大幅降低。假设您要搜索记录 90。在这个新的索引方案中,您首先定位其对应的块指针(此例为 3),然后利用该信息一次就能找到原始数据记录。

因此,本例中的搜索时间为 $\log_{2}8 + 1$ = 4 个时间单位,与之前相比显著减少。

上述例子说明了进行索引的必要性。以下是一些值得注意的要点:

  • 对原始数据记录的排序只能基于一个字段。随后可以按该字段建立索引。只有当您使用该字段进行搜索时,搜索时间才会得到改进。(极其重要)
  • 索引本身也是有序的。
  • 索引记录包含两个字段(索引文件的结构):
    • 原始文件的键
    • 指向原始数据记录中该键所在块的指针
  • 通过索引使用二分查找进行搜索。
  • 使用索引项访问一条记录,平均所需的块访问次数为:
    $\log_{2}B_i + 1$,其中 $B_i$ 为索引记录中的块数
  • 可以在关系的任意字段(主键、候选键、非键)上创建索引。

现在,根据原始数据记录的排序方式以及您在索引文件中维护多少记录,可以设计多种索引方案。下一节将介绍最常见的几种。

不同的索引策略:

  • 稠密索引(Dense Indexing):如果为每个搜索键值都创建索引项,则为稠密索引。请参考下图以获得直观理解。

dense indexing

  • 稀疏索引(Sparse Indexing):如果只为部分记录创建索引项,则为稀疏索引。下图给出了示意。

sparse indexing

上述图示使两种索引方案都易于理解。这里需要强调一点:上述稀疏索引示例其实是稠密与稀疏的组合。这是因为对于每个唯一的搜索键值(1、2 和 3)都有索引,但并非每条数据记录都有索引。

接下来,您将按记录层级来学习其他类型的索引方案。在单级索引中,索引文件只有一个。但有时索引文件会变得非常大,以至于索引文件本身也需要被索引。在这种情况下称为多级索引。让我们进一步探究。

以下是基于层级划分的索引策略概览。

单级索引:

  • 主索引(Primary indexing)
  • 聚簇索引(Clustered indexing)
  • 辅助索引(Secondary indexing)

多级索引:

  • B 树
  • B+ 树

您将学习属于单级索引的所有策略。教程末尾会提供链接,供您在有兴趣时探索多级索引方案。现在先来看主索引。

主索引:

主索引是一个有序文件,其记录为固定长度,包含两个字段:

  • 第一个字段与数据文件的主键相同。
  • 第二个字段是指向主键所在数据块的指针。- 来源

为每个块的第一条记录创建的索引称为块锚(block anchors)。在主索引中,索引项数量 = 原始数据块的数量。使用主索引进行访问的平均块访问次数为:

$\log_{2}B_i + 1$,其中 $B_i$ 为索引记录中的块数。

请参考下图以更清晰地理解:primary indexing

最右侧的图示为被划分为多个块的原始数据记录。注意包含数字 1、2、3、…、9 的列为主键。最左侧的图示为索引项,每个索引项包含:

  • 每个数据块的第一条记录(块锚)
  • 第二个条目表示块指针。

请花点时间思考这属于哪种索引(稀疏还是稠密)。欢迎在评论区留言。

现在来学习聚簇索引。

聚簇索引:

聚簇索引建立在这样的数据文件之上:其文件记录在物理上按照某个非键字段排序,而该字段对每条记录并不具有唯一值。基于该字段进行索引,该字段称为聚簇字段,因此得名聚簇索引。

clustering indexing

图示总能让理解更容易。正如您所见,原始数据记录按某个非键属性排序,并且对该非键属性的每个不同取值都创建一个索引项。使用该方案定位特定记录所需的平均块访问次数为 $\geq$ $\log_{2}B_i + 1$,其中 $B_i$ 为索引记录中的块数。请注意这里的 $\geq$ 符号。

在聚簇索引中,在定位到包含某个键的块之后,您可能还需要遍历其他块(从上图也能明显看出)。

您不妨思考一下这属于哪种索引(稀疏还是稠密)。欢迎在评论区留言。现在来学习辅助索引。

辅助索引:

假设您的数据库中有一张名为 Employee 的表。该表包含以下属性:

  • employee_id
  • employee_name
  • employee_department
  • employee_salary

employee_id 是其主键。现在,您已经基于 employee_id 为该表创建了主索引。但在开发应用时,您发现大多数数据库查询都在使用 employee_name 属性。在这种情况下,主索引作用不大,为 employee_name 的所有取值维护单独的索引会是更好的做法。无论如何,员工姓名在数据库中并不会按有序方式存放。因此,对其建立索引无疑会加速涉及员工姓名的查询。

这就是辅助索引的经典示例。我们也来构建一张合适的示意图:

secondary indexing

如果您有更好的表示方式,欢迎在评论区分享。

接下来看看如何在 PostgreSQL 中创建索引。如果您想掌握其基础知识,可以学习 DataCamp 的 在 PostgreSQL 中进行表连接课程。

在 PostgreSQL 中创建索引

在 PostgreSQL 数据库中开始创建索引之前,您需要在表中准备一些数据。我们来创建一张名为 Student 的简单表,包含以下记录字段:

  • student_id
  • student_name
  • student_year

您将把 student_id 设为主键,并且不允许学生姓名和入学年为空。

对应的查询如下:

CREATE TABLE STUDENT(
   student_id TEXT PRIMARY KEY,
   student_name  TEXT NOT NULL,
   student_year  TEXT NOT NULL
);

表创建成功后,您需要向表中插入一些合适的数据。为方便起见,您可以使用一个 .csv 文件并将其导入 Student。您可以使用以下查询将兼容的 .csv 文件导入 PostgreSQL 表:

COPY STUDENT FROM '/path/to/csv/Student.csv' WITH (FORMAT csv);

上述查询假定用于复制数据的 .csv 文件名为 Student。

现在来看一下数据。运行 select * from STUDENT; 返回如下记录:records

select 查询应返回共 86 条记录。现在,您可以开始创建索引了。您可以使用以下语法创建单列索引:

CREATE INDEX index_name
ON table_name (column_name);

我们在 student_id 字段上创建索引(主索引)。

CREATE INDEX id_index
ON STUDENT (student_id);

也可以创建多列索引:

CREATE INDEX id_index
ON STUDENT (student_id,student_name);

您可以使用以下语法删除索引:

DROP INDEX index_name;

在像 STUDENT 这样的小表中,很难直观感受到索引的作用。但如果表很大(例如包含一所大型大学类似记录的数据库表),索引无疑会发挥关键作用。

您做到了!

恭喜您读到最后。在本教程中,您学习了索引、索引的必要性以及不同的索引方案。您还看到了如何在 PostgreSQL 中执行简单的索引。

不过,本教程未涉及多级索引。如果您有兴趣探索,以下是一些优秀资源:

但使用索引总是有益的吗?其实在某些情况下,不建议使用索引。

indexing guidelines

来源

希望本教程能帮助您厘清索引的基础。欢迎在评论区分享您的有趣发现。

查看 DataCamp 的 SQL 学习中心。

主题