📖 数据密集型设计

索引策略与优化实践

深入探讨数据库索引策略与索引优化实践

一、索引概述

索引是数据库中用于加速数据检索的数据结构。索引通过建立数据的有序结构,将数据查找时间从O(n)降低到O(log n),是数据库性能优化的核心手段。

二、B+树索引原理

2.1 B+树结构

B+树是数据库索引的主要数据结构:

graph TD A[根节点] --> B[分支节点1] A --> C[分支节点2] A --> D[分支节点3] B --> E[叶子节点1] B --> F[叶子节点2] C --> G[叶子节点3] C --> H[叶子节点4] D --> I[叶子节点5] D --> J[叶子节点6] E --> K[数据记录] F --> L[数据记录] G --> M[数据记录] H --> N[数据记录] I --> O[数据记录] J --> P[数据记录]

2.2 B+树特点

  • 平衡树:所有叶子节点深度相同
  • 有序存储:数据按索引键有序排列
  • 范围查询:支持高效的范围查询
  • 全表扫描:叶子节点链表支持全表扫描

2.3 B+树查询过程

sequenceDiagram participant App as 应用 participant Index as B+树索引 participant Data as 数据页 App->>Index: 查询 key=15 Index->>Index: 根节点定位 Index->>Index: 分支节点定位 Index->>Data: 叶子节点查找 Data-->>App: 返回数据记录

三、索引类型

3.1 主键索引

主键索引是唯一且非空的索引:

CREATE TABLE users (
    id INT PRIMARY KEY,  -- 主键索引
    name VARCHAR(50)
);

3.2 唯一索引

唯一索引保证索引列的值唯一:

CREATE UNIQUE INDEX idx_email ON users(email);

3.3 普通索引

普通索引不保证唯一性:

CREATE INDEX idx_name ON users(name);

3.4 复合索引

复合索引包含多个列:

CREATE INDEX idx_user_status ON users(status, created_at);

3.5 全文索引

全文索引用于文本搜索:

CREATE FULLTEXT INDEX idx_content ON articles(content);

3.6 索引类型对比

索引类型 特点 适用场景
主键索引 唯一、非空 主键查询
唯一索引 唯一 唯一约束
普通索引 无约束 普通查询
复合索引 多列 多条件查询
全文索引 文本搜索 全文搜索

四、索引选择策略

4.1 索引列选择原则

  • 高频查询列:经常作为查询条件的列
  • 区分度高的列:列值分布均匀的列
  • 小数据类型列:占用空间小的列
  • 排序分组列:经常用于ORDER BY/GROUP BY的列

4.2 复合索引顺序

复合索引遵循最左前缀原则:

graph LR A[复合索引: a, b, c] --> B[a] A --> C[a, b] A --> D[a, b, c] B --> B1[可使用索引] C --> C1[可使用索引] D --> D1[可使用索引] E[b] --> E1[不可使用索引] F[c] --> F1[不可使用索引] G[b, c] --> G1[不可使用索引]

4.3 索引选择性

索引选择性 = 唯一值数量 / 总行数:

-- 高选择性:适合建索引
SELECT COUNT(DISTINCT email) / COUNT(*) FROM users;  -- 接近1

-- 低选择性:不适合建索引
SELECT COUNT(DISTINCT gender) / COUNT(*) FROM users;  -- 接近0.5

五、索引失效场景

5.1 导致索引失效的操作

操作 示例 说明
函数操作 WHERE YEAR(date) = 2024 索引列上使用函数
类型转换 WHERE id = '123' 隐式类型转换
不等于 WHERE status != 1 不等于操作
LIKE前缀% WHERE name LIKE '%abc' 前缀模糊查询
OR条件 WHERE a=1 OR b=2 OR条件未全部索引
NOT IN WHERE id NOT IN (1,2,3) NOT IN操作

5.2 避免索引失效

-- 避免函数操作
-- 不好
SELECT * FROM orders WHERE YEAR(order_date) = 2024;
-- 好
SELECT * FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-12-31';

-- 避免类型转换
-- 不好
SELECT * FROM users WHERE id = '123';
-- 好
SELECT * FROM users WHERE id = 123;

-- 使用UNION替代OR
-- 不好
SELECT * FROM users WHERE status = 1 OR status = 2;
-- 好
SELECT * FROM users WHERE status = 1
UNION
SELECT * FROM users WHERE status = 2;

六、覆盖索引

6.1 覆盖索引概念

覆盖索引是指索引包含查询所需的所有列:

graph TD A[查询: SELECT name, age FROM users WHERE status=1] B[普通索引 idx_status] --> C[status列] C --> D[回表查询] D --> E[数据页] F[覆盖索引 idx_status_name_age] --> G[status, name, age] G --> H[直接返回] A --> B A --> F

6.2 覆盖索引创建

-- 创建覆盖索引
CREATE INDEX idx_status_name_age ON users(status, name, age);

-- 查询可以使用覆盖索引,无需回表
SELECT name, age FROM users WHERE status = 1;

6.3 覆盖索引适用场景

  • 频繁查询的列组合:经常一起查询的列
  • 统计查询:COUNT、SUM等聚合查询
  • 分页查询:需要排序和分页的查询

七、索引维护

7.1 索引维护成本

操作 索引影响 说明
INSERT 需要更新所有索引
UPDATE 需要更新相关索引
DELETE 需要更新所有索引
SELECT 利用索引加速

7.2 索引数量控制

索引不是越多越好,需要权衡查询和写入性能:

  • 读多写少:可以多建索引
  • 写多读少:尽量少建索引
  • 一般建议:单表索引不超过5个

7.3 索引重建

-- 重建索引
ALTER TABLE users ENGINE=InnoDB;

-- 重新创建索引
DROP INDEX idx_name ON users;
CREATE INDEX idx_name ON users(name);

-- MySQL重建索引
ALTER TABLE users FORCE INDEX(idx_name);

八、索引监控

8.1 查看索引使用情况

-- MySQL查看索引使用
SHOW INDEX FROM users;

-- 查看索引使用统计
SELECT * FROM sys.schema_unused_indexes;

-- PostgreSQL查看索引使用
SELECT * FROM pg_stat_user_indexes;

8.2 索引命中率

-- MySQL查看索引命中率
SELECT 
    SUM(handler_read_key) / (SUM(handler_read_key) + SUM(handler_read_rnd_next)) AS hit_rate
FROM information_schema.global_status;

8.3 慢查询分析

-- 开启慢查询日志
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1

-- 分析慢查询
EXPLAIN SELECT * FROM orders WHERE status = 1;

九、索引最佳实践

9.1 优先使用主键查询

主键查询效率最高,避免全表扫描。

9.2 合理设计复合索引

根据查询模式设计复合索引,遵循最左前缀原则。

9.3 使用覆盖索引

对于频繁查询,使用覆盖索引避免回表。

9.4 定期审查索引

定期检查索引使用情况,删除无用索引。

9.5 注意索引维护成本

在写入密集的表中控制索引数量。

十、总结

索引是数据库性能优化的核心,合理的索引策略能够显著提升查询性能。通过理解B+树原理、选择合适的索引类型、避免索引失效,能够构建高效的数据库查询系统。