第 6 章:索引与性能优化
在处理大量数据时,查询性能成为关键问题。索引是提高数据库查询速度的重要工具。在本章中,我们将学习索引的基本概念、类型、创建方法以及如何通过索引优化查询性能。
本章内容概要
- 什么是索引?
- 索引的类型(主键索引、唯一索引、普通索引、全文索引)
- 创建与删除索引
- 索引的工作原理
- 索引的优点与缺点
- 查询性能分析(EXPLAIN)
- SQL 优化技巧
- 实战练习:优化慢查询
什么是索引?
索引是数据库中一种特殊的数据结构,类似于书籍的目录。它可以帮助数据库系统快速定位到表中的特定记录,而不需要扫描整个表。
索引的作用
- 提高查询速度 - 通过索引可以直接定位到目标数据
- 加速排序 - 索引本身是有序的,可以加速 ORDER BY 操作
- 保证唯一性 - 唯一索引可以确保数据的唯一性
- 加速表连接 - 在表连接操作中,索引可以提高连接速度
索引的代价
虽然索引有很多优点,但也有一些代价:
- 占用存储空间 - 索引需要额外的存储空间
- 降低写入性能 - 插入、更新、删除数据时需要维护索引
- 增加复杂性 - 需要合理设计和维护索引
索引的类型
主键索引(Primary Key Index)
主键索引是一种特殊的唯一索引,不允许有重复值和空值。每个表只能有一个主键索引。
sql
-- 创建表时定义主键索引
CREATE TABLE students (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
student_number VARCHAR(20) NOT NULL
);
-- 或者明确指定主键索引
CREATE TABLE students (
id INT AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
student_number VARCHAR(20) NOT NULL,
PRIMARY KEY (id)
);唯一索引(Unique Index)
唯一索引确保索引列中的值是唯一的,但允许有空值。
sql
-- 创建表时定义唯一索引
CREATE TABLE students (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
student_number VARCHAR(20) NOT NULL UNIQUE
);
-- 或者明确指定唯一索引
CREATE TABLE students (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
student_number VARCHAR(20) NOT NULL,
UNIQUE KEY uk_student_number (student_number)
);
-- 在已存在的表上创建唯一索引
CREATE UNIQUE INDEX uk_student_number ON students(student_number);普通索引(Normal Index)
普通索引是最基本的索引类型,没有任何限制。
sql
-- 创建表时定义普通索引
CREATE TABLE students (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
student_number VARCHAR(20) NOT NULL,
major VARCHAR(50),
INDEX idx_major (major)
);
-- 在已存在的表上创建普通索引
CREATE INDEX idx_major ON students(major);全文索引(Fulltext Index)
全文索引主要用于文本搜索,支持复杂的文本查询操作。MySQL 5.6 之后,InnoDB 存储引擎也支持全文索引。
sql
-- 创建表时定义全文索引
CREATE TABLE articles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200),
content TEXT,
FULLTEXT KEY ft_title_content (title, content)
);
-- 在已存在的表上创建全文索引
CREATE FULLTEXT INDEX ft_title_content ON articles(title, content);组合索引(Composite Index)
组合索引是在多个列上创建的索引。
sql
-- 创建表时定义组合索引
CREATE TABLE grades (
id INT AUTO_INCREMENT PRIMARY KEY,
student_id INT NOT NULL,
course_id INT NOT NULL,
grade DECIMAL(5,2),
INDEX idx_student_course (student_id, course_id)
);
-- 在已存在的表上创建组合索引
CREATE INDEX idx_student_course ON grades(student_id, course_id);创建与删除索引
创建索引的方法
1. 使用 CREATE INDEX 语句
sql
-- 创建普通索引
CREATE INDEX idx_name ON students(name);
-- 创建唯一索引
CREATE UNIQUE INDEX uk_email ON students(email);
-- 创建组合索引
CREATE INDEX idx_major_name ON students(major, name);
-- 创建前缀索引(适用于长字符串)
CREATE INDEX idx_address ON students(address(10));2. 使用 ALTER TABLE 语句
sql
-- 添加普通索引
ALTER TABLE students ADD INDEX idx_phone (phone);
-- 添加唯一索引
ALTER TABLE students ADD UNIQUE INDEX uk_phone (phone);
-- 添加主键索引
ALTER TABLE students ADD PRIMARY KEY (id);删除索引
sql
-- 使用 DROP INDEX 删除索引
DROP INDEX idx_name ON students;
-- 使用 ALTER TABLE 删除索引
ALTER TABLE students DROP INDEX idx_name;
-- 删除主键索引
ALTER TABLE students DROP PRIMARY KEY;索引的工作原理
B+树索引结构
MySQL 中的 InnoDB 存储引擎使用 B+树作为索引的数据结构:
- 叶子节点 - 存储实际的数据记录或主键值
- 非叶子节点 - 存储索引键值和指向子节点的指针
- 有序性 - 索引按键值有序排列
- 平衡性 - 所有叶子节点到根节点的距离相同
索引查找过程
当执行查询时,MySQL 会:
- 根据查询条件找到对应的索引
- 在索引树中查找匹配的键值
- 通过索引找到对应的数据记录
- 返回查询结果
索引的优点与缺点
优点
- 大幅提升查询速度 - 特别是 WHERE 条件和 ORDER BY 操作
- 保证数据唯一性 - 唯一索引可以防止重复数据
- 加速表连接 - 在 JOIN 操作中提高性能
- 优化分组和排序 - 加速 GROUP BY 和 ORDER BY 操作
缺点
- 占用额外存储空间 - 索引本身需要存储空间
- 降低写入性能 - INSERT、UPDATE、DELETE 需要维护索引
- 增加维护成本 - 需要定期优化和维护索引
- 不当使用可能适得其反 - 过多或不当的索引反而会降低性能
查询性能分析(EXPLAIN)
EXPLAIN 命令可以帮助我们分析 SQL 查询的执行计划。
基本用法
sql
-- 分析查询执行计划
EXPLAIN SELECT * FROM students WHERE name = '张三';
-- 更详细的分析
EXPLAIN FORMAT=JSON SELECT * FROM students WHERE name = '张三';EXPLAIN 输出字段说明
| 字段 | 说明 |
|---|---|
| id | 查询序列号 |
| select_type | 查询类型 |
| table | 表名 |
| partitions | 匹配的分区 |
| type | 连接类型 |
| possible_keys | 可能使用的索引 |
| key | 实际使用的索引 |
| key_len | 索引长度 |
| ref | 索引比较的列 |
| rows | 扫描的行数 |
| filtered | 过滤百分比 |
| Extra | 额外信息 |
连接类型(type)说明
按性能从好到差排序:
- system - 表只有一行记录(系统表)
- const - 通过主键或唯一索引查找,最多只有一行匹配
- eq_ref - 对于每个来自前表的行组合,从该表中读取一行
- ref - 使用非唯一索引查找
- range - 只检索给定范围的行
- index - 全索引扫描
- ALL - 全表扫描(应尽量避免)
SQL 优化技巧
1. 合理使用索引
sql
-- 好的查询:使用索引
SELECT * FROM students WHERE student_number = '2023001';
-- 不好的查询:没有使用索引
SELECT * FROM students WHERE name LIKE '%张%';2. 避免在索引列上使用函数
sql
-- 不好的查询:无法使用索引
SELECT * FROM students WHERE YEAR(enrollment_date) = 2023;
-- 好的查询:可以使用索引
SELECT * FROM students WHERE enrollment_date >= '2023-01-01' AND enrollment_date < '2024-01-01';3. 使用组合索引时遵循最左前缀原则
sql
-- 假设有组合索引 idx_major_name (major, name)
-- 可以使用索引的查询
SELECT * FROM students WHERE major = '计算机科学与技术';
SELECT * FROM students WHERE major = '计算机科学与技术' AND name = '张三';
-- 无法使用索引的查询
SELECT * FROM students WHERE name = '张三';4. 优化 LIMIT 分页查询
sql
-- 不好的分页:深度分页性能差
SELECT * FROM students ORDER BY id LIMIT 100000, 10;
-- 好的分页:使用索引定位
SELECT * FROM students WHERE id > 100000 ORDER BY id LIMIT 10;实战练习:优化慢查询
让我们通过实际例子来练习索引优化。
1. 创建测试数据
sql
USE student_management;
-- 创建一个大表用于测试
CREATE TABLE large_students (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
student_number VARCHAR(20) NOT NULL UNIQUE,
major VARCHAR(50),
class_name VARCHAR(50),
enrollment_date DATE,
INDEX idx_major (major),
INDEX idx_enrollment_date (enrollment_date)
);
-- 插入大量测试数据(简化版)
INSERT INTO large_students (name, student_number, major, class_name, enrollment_date)
SELECT
CONCAT('学生', s1.n + s2.n * 10 + s3.n * 100) AS name,
CONCAT('2023', LPAD(s1.n + s2.n * 10 + s3.n * 100, 4, '0')) AS student_number,
CASE (s1.n + s2.n * 10) % 5
WHEN 0 THEN '计算机科学与技术'
WHEN 1 THEN '软件工程'
WHEN 2 THEN '数据科学与大数据技术'
WHEN 3 THEN '人工智能'
ELSE '网络安全'
END AS major,
CONCAT('班级', (s1.n + s2.n * 10) % 10 + 1) AS class_name,
DATE_ADD('2023-09-01', INTERVAL (s1.n + s2.n * 10) DAY) AS enrollment_date
FROM
(SELECT 0 n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) s1,
(SELECT 0 n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) s2,
(SELECT 0 n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) s3
LIMIT 1000;2. 分析慢查询
sql
-- 分析没有索引的查询
EXPLAIN SELECT * FROM large_students WHERE name LIKE '学生5%';
-- 分析使用索引的查询
EXPLAIN SELECT * FROM large_students WHERE major = '计算机科学与技术';
-- 分析范围查询
EXPLAIN SELECT * FROM large_students WHERE enrollment_date BETWEEN '2023-09-01' AND '2023-09-30';3. 创建合适的索引
sql
-- 为姓名创建前缀索引
CREATE INDEX idx_name ON large_students(name(10));
-- 为组合查询创建组合索引
CREATE INDEX idx_major_enrollment ON large_students(major, enrollment_date);4. 优化后再次分析
sql
-- 优化后的查询分析
EXPLAIN SELECT * FROM large_students WHERE name LIKE '学生5%';
EXPLAIN SELECT * FROM large_students WHERE major = '计算机科学与技术' AND enrollment_date > '2023-09-15';5. 全文搜索优化
sql
-- 创建支持全文搜索的表
CREATE TABLE articles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200),
content TEXT,
FULLTEXT KEY ft_title_content (title, content)
);
-- 插入测试数据
INSERT INTO articles (title, content) VALUES
('MySQL索引优化', 'MySQL索引是提高查询性能的重要手段...'),
('数据库设计原则', '良好的数据库设计是系统性能的基础...'),
('SQL查询优化技巧', '编写高效的SQL查询语句是数据库优化的关键...');
-- 使用全文搜索
SELECT * FROM articles WHERE MATCH(title, content) AGAINST('索引优化');索引维护
查看索引信息
sql
-- 查看表的索引信息
SHOW INDEX FROM students;
-- 查看表的状态信息
SHOW TABLE STATUS LIKE 'students';分析表
sql
-- 分析表的索引分布
ANALYZE TABLE students;优化表
sql
-- 优化表结构和索引
OPTIMIZE TABLE students;小结
在本章中,我们学习了索引与性能优化的重要知识:
- 索引基础 - 索引的概念、作用和代价
- 索引类型 - 主键索引、唯一索引、普通索引、全文索引、组合索引
- 索引操作 - 创建、删除索引的方法
- 索引原理 - B+树结构和索引查找过程
- 性能分析 - 使用 EXPLAIN 分析查询执行计划
- 优化技巧 - SQL 优化的最佳实践
- 实战练习 - 通过实际例子练习索引优化
掌握索引和性能优化技能对于构建高性能的数据库应用至关重要。在下一章中,我们将学习视图与存储过程,这些是数据库高级功能的重要组成部分。