Lazy loaded image
13_索引
Words 5511Read Time 14 min
2025-12-11

第13章 索引

13.1 索引概述

13.1.1 索引的概念

索引(Index)是数据库中用于提高查询性能的数据结构,它通过建立数据值与存储位置之间的映射关系,加速数据的检索过程。索引类似于书籍的目录,可以帮助数据库系统快速定位到需要的数据,而不需要扫描整个表。

13.1.2 索引的作用

  • 提高查询速度:通过索引可以直接定位到数据所在的物理位置,避免全表扫描
  • 加速排序和分组:索引本身是有序的,可以直接用于排序和分组操作
  • 保证数据唯一性:唯一索引可以确保数据的唯一性约束
  • 加速连接查询:在连接查询中,索引可以加速表之间的关联

13.1.3 索引的优缺点

优点

  • 显著提高查询速度
  • 减少IO操作
  • 加速排序和分组
  • 保证数据完整性

缺点

  • 占用额外的磁盘空间
  • 降低数据插入、更新和删除的性能
  • 需要维护索引结构,增加系统开销
  • 过多的索引会影响查询优化器的选择

13.2 索引的工作原理

13.2.1 索引的基本原理

索引的基本原理是通过构建高效的数据结构,将数据列的值与数据行的物理地址关联起来。当执行查询时,数据库系统首先查询索引,找到匹配的数据行地址,然后根据地址直接访问数据行,从而避免全表扫描。

13.2.2 索引的存储结构

索引通常使用以下数据结构:

13.2.2.1 B树索引

B树(Balance Tree)是一种平衡多路查找树,具有以下特点: - 每个节点可以包含多个关键字 - 根节点至少有2个子节点 - 除根节点外,每个节点至少有m/2个子节点(m为B树的阶) - 所有叶子节点在同一层 - 关键字按顺序排列
B树的优点是: - 平衡结构,查询效率稳定 - 适合范围查询 - 适合随机访问

13.2.2.2 B+树索引

B+树是B树的变种,是数据库系统中最常用的索引结构,具有以下特点: - 所有关键字都存储在叶子节点中 - 叶子节点之间通过链表连接,形成有序链表 - 非叶子节点只存储关键字和指针,不存储数据 - 叶子节点存储完整的关键字和数据行指针
B+树相比B树的优点: - 叶子节点有序链表,更适合范围查询 - 非叶子节点不存储数据,相同大小的节点可以存储更多关键字 - 查询效率稳定,始终需要访问到叶子节点

13.2.2.3 哈希索引

哈希索引基于哈希表实现,具有以下特点: - 直接通过哈希函数计算数据位置 - 适合等值查询,不适合范围查询 - 查询速度快,时间复杂度为O(1) - 不支持排序

13.2.2.4 全文索引

全文索引用于全文搜索,能够快速查找包含特定词语的文本数据。

13.2.3 索引的查找过程

以B+树索引为例,查找过程如下: 1. 从根节点开始,比较查询值与节点中的关键字 2. 根据比较结果,选择对应的子节点继续查找 3. 重复上述过程,直到到达叶子节点 4. 在叶子节点中查找匹配的关键字,获取对应的数据行指针 5. 根据数据行指针访问实际的数据行

13.3 索引的类型

13.3.1 按数据结构分类

  • B+树索引:最常用的索引类型,适合范围查询和等值查询
  • 哈希索引:适合等值查询,不适合范围查询
  • 全文索引:用于全文搜索
  • 空间索引:用于地理空间数据查询

13.3.2 按功能分类

13.3.2.1 普通索引

普通索引是最基本的索引类型,没有任何限制,用于加速数据查询。

13.3.2.2 唯一索引

唯一索引要求索引列的值必须唯一,但允许空值(最多一个NULL)。

13.3.2.3 主键索引

主键索引是一种特殊的唯一索引,不允许空值,一个表只能有一个主键索引。

13.3.2.4 复合索引

复合索引是基于多个列创建的索引,遵循最左前缀原则。

13.3.2.5 前缀索引

前缀索引是对字符串列的前几个字符创建索引,适用于长字符串列,可以减少索引的存储空间。

13.3.2.6 全文索引

全文索引用于全文搜索,支持在文本数据中查找特定词语。

13.3.3 按存储方式分类

13.3.3.1 聚簇索引

聚簇索引(Clustered Index)决定了数据行的物理存储顺序,叶子节点存储的是完整的数据行。一个表只能有一个聚簇索引。

13.3.3.2 非聚簇索引

非聚簇索引(Non-Clustered Index)的叶子节点存储的是数据行的指针或主键值,而不是完整的数据行。一个表可以有多个非聚簇索引。

13.3.4 聚簇索引与非聚簇索引的区别

特性
聚簇索引
非聚簇索引
数量限制
一个表只能有一个
一个表可以有多个
物理存储
决定数据行的物理存储顺序
不影响数据行的物理存储顺序
叶子节点内容
完整的数据行
数据行指针或主键值
查询效率
通常更高
通常低于聚簇索引
适用场景
主键或频繁查询的列
频繁查询的非主键列

13.4 索引的创建与管理

13.4.1 创建索引

13.4.1.1 创建表时创建索引

13.4.1.2 表创建后创建索引

13.4.2 查看索引

13.4.3 修改索引

在大多数数据库中,没有直接修改索引的语句,通常需要先删除索引,然后重新创建。

13.4.4 删除索引

13.5 索引的使用场景

13.5.1 适合创建索引的场景

  • 频繁作为查询条件的列:如WHERE子句中经常出现的列
  • 经常用于连接的列:如外键列,用于表连接
  • 经常需要排序的列:如ORDER BY子句中的列
  • 经常需要分组的列:如GROUP BY子句中的列
  • 唯一约束的列:如身份证号、手机号等
  • 字符串列的前缀:对于长字符串列,可以创建前缀索引

13.5.2 不适合创建索引的场景

  • 数据量小的表:表数据量小时,全表扫描可能比索引查询更快
  • 频繁更新的列:更新列时需要同时更新索引,会降低性能
  • 值分布均匀的列:如性别列(只有男/女),索引选择性低
  • 很少使用的列:创建索引会占用空间,且不会带来查询性能提升
  • TEXT、BLOB等大字段:这些列的数据量大,创建索引会占用大量空间

13.6 索引的设计原则

13.6.1 最左前缀原则

复合索引遵循最左前缀原则,即查询条件中使用了复合索引的第一个列,索引才会被使用。
例如,创建复合索引INDEX idx_name_age(name, age),则: - WHERE name = '张三':会使用索引 - WHERE name = '张三' AND age = 18:会使用索引 - WHERE age = 18:不会使用索引

13.6.2 选择高选择性的列作为索引

索引的选择性是指不重复的索引值与表记录数的比值,选择性越高,索引的查询效率越高。
例如,身份证号的选择性接近1,适合创建索引;而性别的选择性只有0.5,不适合创建索引。

13.6.3 避免过度索引

  • 每个索引都需要占用额外的磁盘空间
  • 索引会降低插入、更新和删除的性能
  • 过多的索引会影响查询优化器的选择

13.6.4 考虑索引的存储成本

  • 对于大表,索引的存储成本可能很高
  • 可以考虑使用前缀索引减少索引的存储空间
  • 定期清理无用的索引

13.6.5 合理使用复合索引

  • 复合索引可以替代多个单列索引
  • 复合索引的列顺序应该根据查询频率和选择性来确定
  • 将最常用的列放在前面
  • 将选择性高的列放在前面

13.6.6 考虑查询优化器的选择

  • 查询优化器会根据索引的选择性、数据分布等因素选择是否使用索引
  • 有时查询优化器可能会选择全表扫描而不是使用索引

13.7 索引的使用与优化

13.7.1 索引的使用

13.7.1.1 索引的触发条件

  • 查询条件中使用了索引列(遵循最左前缀原则)
  • 索引列上没有使用函数或表达式
  • 索引列的数据类型与查询条件的数据类型匹配

13.7.1.2 索引失效的情况

  • 在索引列上使用函数或表达式
    • 使用!=或<>运算符
      • 使用IS NULL或IS NOT NULL
        • 使用OR连接条件(如果OR两边的列都有索引,可能会使用索引)
          • 使用LIKE以通配符开头
            • 数据类型不匹配

              13.7.2 索引的优化策略

              13.7.2.1 分析查询性能

              使用EXPLAIN命令分析查询语句的执行计划,查看是否使用了索引。

              13.7.2.2 优化查询语句

              • 避免在索引列上使用函数或表达式
              • 避免使用!=或<>运算符
              • 避免使用LIKE以通配符开头
              • 使用合适的数据类型
              • 合理使用连接条件

              13.7.2.3 优化索引结构

              • 根据查询需求调整复合索引的列顺序
              • 考虑使用覆盖索引,避免回表查询
              • 定期重建索引,优化索引碎片

              13.7.2.4 使用覆盖索引

              覆盖索引是指查询的所有列都包含在索引中,不需要回表查询实际的数据行。

              13.7.2.5 避免回表查询

              回表查询是指通过索引找到数据行指针后,还需要访问实际的数据行。可以通过使用覆盖索引或主键索引来避免回表查询。

              13.8 索引的维护与监控

              13.8.1 索引的维护

              13.8.1.1 重建索引

              当索引出现碎片或性能下降时,可以重建索引。

              13.8.1.2 优化索引

              优化索引可以消除索引碎片,提高索引性能。

              13.8.1.3 删除无用索引

              定期检查并删除无用的索引,减少存储开销和维护成本。

              13.8.2 索引的监控

              13.8.2.1 监控索引使用情况

              可以通过数据库系统视图或工具监控索引的使用情况,包括: - 索引的查询次数 - 索引的命中率 - 索引的更新次数 - 索引的大小

              13.8.2.2 识别低效索引

              低效索引的特征: - 很少被使用的索引 - 选择性低的索引 - 更新频繁的索引 - 占用大量空间的索引

              13.8.2.3 识别缺失索引

              通过分析查询日志和执行计划,可以识别出应该创建但尚未创建的索引。

              13.9 索引的最佳实践

              13.9.1 索引设计最佳实践

              1. 根据查询需求设计索引:分析常用的查询语句,针对查询条件设计索引
              1. 遵循最左前缀原则:复合索引的列顺序要根据查询频率和选择性来确定
              1. 选择高选择性的列:索引选择性越高,查询效率越高
              1. 避免过度索引:每个表的索引数量不宜过多,一般不超过5个
              1. 合理使用复合索引:复合索引可以替代多个单列索引
              1. 考虑索引的存储成本:对于大表,要考虑索引的存储空间
              1. 定期维护索引:重建或优化索引,消除索引碎片
              1. 监控索引使用情况:定期检查索引的使用情况,删除无用索引

              13.9.2 索引使用最佳实践

              1. 避免在索引列上使用函数:会导致索引失效
              1. 使用合适的查询条件:避免使用!=、IS NULL等可能导致索引失效的条件
              1. 使用覆盖索引:减少回表查询,提高查询效率
              1. 合理使用连接条件:连接查询中,确保连接列上有索引
              1. 分析查询执行计划:使用EXPLAIN命令分析查询语句,优化查询
              1. 考虑数据类型匹配:查询条件的数据类型要与索引列的数据类型匹配

              13.10 综合示例

              13.10.1 示例1:用户表索引设计

              假设有一个用户表users,包含以下字段: - id(主键,自增) - username(用户名,唯一) - email(邮箱,唯一) - phone(手机号) - age(年龄) - gender(性别) - created_at(创建时间)

              13.10.1.1 常用查询场景

              1. 根据用户名查询用户:SELECT * FROM users WHERE username = '张三';
              1. 根据邮箱查询用户:SELECT * FROM users WHERE email = 'zhangsan@example.com';
              1. 查询2023年创建的用户:SELECT * FROM users WHERE YEAR(created_at) = 2023;
              1. 查询年龄大于18岁的用户:SELECT * FROM users WHERE age > 18 ORDER BY created_at DESC;
              1. 查询男性用户,按年龄降序排序:SELECT * FROM users WHERE gender = '男' ORDER BY age DESC;

              13.10.1.2 索引设计

              13.10.2 示例2:订单表索引设计

              假设有一个订单表orders,包含以下字段: - order_id(订单ID,主键) - user_id(用户ID,外键) - product_id(产品ID,外键) - order_date(订单日期) - total_amount(订单总金额) - status(订单状态)

              13.10.2.1 常用查询场景

              1. 根据订单ID查询订单:SELECT * FROM orders WHERE order_id = '123456';
              1. 查询用户的所有订单:SELECT * FROM orders WHERE user_id = 1001 ORDER BY order_date DESC;
              1. 查询2023年1月的订单:SELECT * FROM orders WHERE order_date BETWEEN '2023-01-01' AND '2023-01-31';
              1. 查询状态为”已完成”的订单:SELECT * FROM orders WHERE status = '已完成' ORDER BY order_date DESC;
              1. 查询用户2023年的订单总金额:SELECT SUM(total_amount) FROM orders WHERE user_id = 1001 AND YEAR(order_date) = 2023;

              13.10.2.2 索引设计

              13.11 常见问题与解决方案

              13.11.1 问题1:索引没有被使用

              • 原因:查询条件不符合索引的使用规则,如在索引列上使用了函数、使用了!=运算符等
              • 解决方案:优化查询语句,避免在索引列上使用函数,使用合适的查询条件

              13.11.2 问题2:索引过多导致性能下降

              • 原因:过多的索引会占用额外的磁盘空间,降低插入、更新和删除的性能
              • 解决方案:删除无用的索引,合并重复的索引,根据查询需求优化索引结构

              13.11.3 问题3:索引碎片导致性能下降

              • 原因:频繁的插入、更新和删除操作会导致索引碎片
              • 解决方案:定期重建或优化索引,消除索引碎片

              13.11.4 问题4:复合索引的列顺序不合理

              • 原因:复合索引的列顺序没有遵循最左前缀原则,导致索引失效
              • 解决方案:根据查询频率和选择性调整复合索引的列顺序

              13.11.5 问题5:回表查询过多

              • 原因:查询的列不在索引中,需要回表查询实际的数据行
              • 解决方案:使用覆盖索引,将查询的列包含在索引中

              13.12 小结

              索引是数据库中提高查询性能的重要工具,通过合理设计和使用索引,可以显著提高数据库的查询效率。本章介绍了索引的基本概念、工作原理、类型、创建和管理方法,以及索引的使用场景和最佳实践。
              在实际应用中,需要根据具体的查询需求和数据特点,合理设计和优化索引。同时,还需要定期维护和监控索引,确保索引的性能和有效性。
              索引的设计是一个权衡的过程,需要在查询性能和写入性能之间找到平衡。过多的索引会降低写入性能,而过少的索引会导致查询性能下降。因此,需要根据实际情况,选择合适的索引策略。

              13.13 练习题

              1. 什么是索引?索引的作用是什么?
              1. 索引有哪些类型?请简要描述每种类型的特点。
              1. B树索引和B+树索引的区别是什么?
              1. 什么是聚簇索引和非聚簇索引?它们的区别是什么?
              1. 复合索引的最左前缀原则是什么?请举例说明。
              1. 哪些情况下索引会失效?请举例说明。
              1. 请描述适合创建索引的场景和不适合创建索引的场景。
              1. 索引设计的原则有哪些?
              1. 什么是覆盖索引?它有什么优点?
              1. 如何监控和维护索引?
              上一篇
              openGauss数据库
              下一篇
              openGauss数据库
              Catalog