1. 索引基础概念与工作原理
在数据库系统中,索引就像图书馆的图书目录卡,它能帮助数据库引擎快速定位到表中的特定数据行,而不必扫描整个表。MySQL中最常用的索引类型是B-Tree索引(实际实现为B+Tree),这种数据结构特别适合处理范围查询和等值查询。
1.1 索引的物理存储结构
MySQL的InnoDB存储引擎中,索引和数据实际上是存储在一起的。主键索引(聚簇索引)的叶子节点直接包含完整的数据行,而非主键索引(二级索引)的叶子节点则存储主键值。这种设计带来几个重要特性:
- 通过主键查询时只需一次索引查找即可获取完整数据
- 通过二级索引查询需要两次查找(先查二级索引获取主键,再通过主键查数据)
- 表数据本身就是按主键顺序组织的物理存储
1.2 索引的代价与选择
虽然索引能极大提高查询速度,但并非没有代价:
- 存储空间:每个索引都需要额外的磁盘空间,大型表的索引可能比数据本身还大
- 写入开销:每次INSERT、UPDATE、DELETE操作都需要更新所有相关索引
- 维护成本:索引需要定期维护以保证性能,碎片化的索引会降低查询效率
经验法则:只为高频查询条件创建索引,避免过度索引。通常,一个表有3-5个精心设计的索引就足够了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 索引创建与删除操作
2.1 创建索引的SQL语法
MySQL提供了多种创建索引的方式,每种适用于不同场景:
sql复制-- 方式1:建表时直接定义
CREATE TABLE users (
id INT AUTO_INCREMENT,
username VARCHAR(50),
email VARCHAR(100),
created_at TIMESTAMP,
PRIMARY KEY (id),
INDEX idx_username (username),
UNIQUE INDEX idx_email (email)
);
-- 方式2:使用ALTER TABLE添加索引
ALTER TABLE users ADD INDEX idx_created_at (created_at);
-- 方式3:使用CREATE INDEX语句(MySQL特有语法)
CREATE INDEX idx_username_email ON users(username, email);
2.2 索引删除操作
删除不需要的索引可以节省存储空间并提高写入性能:
sql复制-- 方式1:使用DROP INDEX语句
DROP INDEX idx_username ON users;
-- 方式2:使用ALTER TABLE删除
ALTER TABLE users DROP INDEX idx_email;
注意:删除主键索引需要特别处理,必须先删除所有外键约束或使用特殊语法:
sql复制ALTER TABLE users DROP PRIMARY KEY;
2.3 索引创建的最佳实践
- 为WHERE子句中的列创建索引
- 为JOIN操作的连接条件列创建索引
- 考虑为ORDER BY和GROUP BY的列创建索引
- 高选择性列更适合建索引(如用户ID比性别更适合)
- 避免在频繁更新的列上创建过多索引
3. 复合索引设计与使用
3.1 复合索引的最左前缀原则
复合索引(多列索引)遵循最左前缀匹配原则。例如创建了索引(A,B,C),以下查询能利用该索引:
sql复制WHERE A = 'value'
WHERE A = 'value' AND B > 42
WHERE A = 'value' AND B = 'value' AND C = 'value'
但以下查询无法充分利用该索引:
sql复制WHERE B = 'value' -- 缺少最左列A
WHERE A = 'value' AND C = 'value' -- 跳过中间列B
3.2 复合索引列顺序选择
设计复合索引时,列顺序至关重要。一般原则:
- 选择性高的列放前面
- 等值查询的列放前面,范围查询的列放后面
- 经常一起查询的列组合应该建在一起
例如,对于查询:
sql复制SELECT * FROM orders
WHERE user_id = 123 AND status = 'completed'
ORDER BY created_at DESC;
最优索引可能是(user_id, status, created_at),因为:
- user_id是高选择性等值条件
- status是低选择性等值条件
- created_at用于排序
3.3 覆盖索引优化
当索引包含查询所需的所有列时,MySQL可以直接从索引获取数据而无需回表,这称为"覆盖索引"。例如:
sql复制-- 假设有索引 (user_id, status)
SELECT user_id, status FROM orders WHERE user_id = 123;
这种查询性能极高,因为引擎只需读取索引,不需访问数据行。
4. 索引性能分析与优化
4.1 使用EXPLAIN分析索引使用情况
EXPLAIN命令是分析查询性能的核心工具:
sql复制EXPLAIN SELECT * FROM users WHERE username = 'john';
关键列解读:
- type:从最好到最差依次为 system > const > eq_ref > ref > range > index > ALL
- possible_keys:可能使用的索引
- key:实际使用的索引
- rows:预估需要检查的行数
- Extra:额外信息,如"Using index"表示使用了覆盖索引
4.2 索引失效的常见场景
即使创建了索引,以下情况仍可能导致索引失效:
-
对索引列使用函数或运算:
sql复制WHERE YEAR(created_at) = 2023 -- 索引失效 -
使用LIKE以通配符开头:
sql复制WHERE username LIKE '%john%' -- 索引失效 -
隐式类型转换:
sql复制WHERE user_id = '123' -- 如果user_id是INT类型 -
使用OR条件:
sql复制WHERE user_id = 123 OR status = 'active' -- 如果两个列都有索引,可能只用一个 -
使用NOT、!=、<>等否定操作符
4.3 索引维护与优化
定期维护索引可以保证最佳性能:
-
分析索引使用情况:
sql复制SELECT * FROM sys.schema_unused_indexes; -
重建碎片化索引:
sql复制ALTER TABLE users ENGINE=InnoDB; -- 重建表 ANALYZE TABLE users; -- 更新统计信息 -
监控索引效率:
sql复制SELECT index_name, rows_read, rows_inserted FROM performance_schema.table_io_waits_summary_by_index_usage;
5. 特殊类型索引的应用
5.1 全文索引与搜索优化
对于文本内容的搜索,普通索引效率低下,全文索引是更好的选择:
sql复制-- 创建全文索引
ALTER TABLE articles ADD FULLTEXT INDEX ft_index (title, content);
-- 使用全文搜索
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('数据库优化' IN NATURAL LANGUAGE MODE);
全文索引使用倒排索引结构,支持自然语言搜索和布尔搜索模式。
5.2 空间索引与地理位置查询
MySQL支持空间数据类型和R-Tree空间索引:
sql复制CREATE TABLE locations (
id INT PRIMARY KEY,
name VARCHAR(100),
position POINT NOT NULL,
SPATIAL INDEX(position)
);
-- 查询5公里范围内的地点
SELECT id, name, ST_Distance_Sphere(position, POINT(116.404, 39.915)) as distance
FROM locations
WHERE ST_Distance_Sphere(position, POINT(116.404, 39.915)) <= 5000
ORDER BY distance;
5.3 哈希索引与内存表
MEMORY存储引擎支持真正的哈希索引,适用于等值查询:
sql复制CREATE TABLE session_data (
session_id CHAR(32) PRIMARY KEY,
user_data TEXT
) ENGINE=MEMORY;
-- 自动使用哈希索引进行快速查找
SELECT * FROM session_data WHERE session_id = 'abc123';
但哈希索引不支持范围查询和排序操作。
6. 索引设计实战案例
6.1 电商系统索引设计
典型电商订单表可能需要以下索引:
sql复制CREATE TABLE orders (
order_id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
status ENUM('pending','paid','shipped','completed','cancelled') NOT NULL,
total_amount DECIMAL(10,2) NOT NULL,
created_at DATETIME NOT NULL,
paid_at DATETIME,
INDEX idx_user_status (user_id, status),
INDEX idx_created (created_at),
INDEX idx_status_created (status, created_at)
);
这样设计可以高效支持:
- 用户查看自己的订单(user_id)
- 按状态筛选订单(status)
- 查看最新订单(created_at)
- 后台处理待支付订单(status + created_at)
6.2 社交网络好友关系设计
好友关系表需要特殊索引设计:
sql复制CREATE TABLE friendships (
user_id BIGINT NOT NULL,
friend_id BIGINT NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (user_id, friend_id),
INDEX idx_friend_user (friend_id, user_id)
);
-- 查询用户的所有好友
SELECT * FROM friendships WHERE user_id = 123;
-- 查询谁把用户加为好友
SELECT * FROM friendships WHERE friend_id = 123;
这种双向索引设计确保了两种查询方向都有良好性能。
6.3 日志表的时间范围查询优化
对于日志类时间序列数据:
sql复制CREATE TABLE access_logs (
log_id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT,
action VARCHAR(50) NOT NULL,
created_at DATETIME NOT NULL,
INDEX idx_time_action (created_at, action),
INDEX idx_user_time (user_id, created_at)
) PARTITION BY RANGE (TO_DAYS(created_at)) (
PARTITION p202301 VALUES LESS THAN (TO_DAYS('2023-02-01')),
PARTITION p202302 VALUES LESS THAN (TO_DAYS('2023-03-01')),
-- 更多分区...
);
结合分区和索引可以极大提高时间范围查询性能。
7. 索引监控与问题排查
7.1 识别缺失索引
MySQL提供了识别潜在缺失索引的方法:
sql复制-- 查看未使用索引的查询
SELECT * FROM performance_schema.events_statements_summary_by_digest
WHERE digest_text LIKE 'SELECT%'
ORDER BY sum_timer_wait DESC LIMIT 10;
-- 使用sys库分析
SELECT * FROM sys.schema_index_statistics
WHERE index_name IS NULL;
7.2 解决索引冲突
当多个索引可能被使用时,MySQL可能选择不理想的索引:
sql复制-- 强制使用特定索引
SELECT * FROM users FORCE INDEX (idx_username) WHERE username = 'john';
-- 忽略特定索引
SELECT * FROM users IGNORE INDEX (idx_email) WHERE email = 'john@example.com';
7.3 索引统计信息更新
MySQL依赖统计信息来选择索引,过时的统计会导致性能问题:
sql复制-- 手动更新表统计信息
ANALYZE TABLE users;
-- 查看索引统计
SHOW INDEX FROM users;
关键指标:
- Cardinality:索引中唯一值的估计数量
- Index_type:索引类型(BTREE, HASH等)
- Comment:索引附加信息
8. 高级索引优化技巧
8.1 索引条件下推(ICP)
ICP是MySQL 5.6引入的重要优化,允许在存储引擎层过滤数据:
sql复制-- 假设有索引 (a,b)
SELECT * FROM table WHERE a = 'value' AND b LIKE '%term%';
没有ICP时,存储引擎会返回所有a='value'的行,然后在服务器层过滤b条件。启用ICP后,存储引擎会直接过滤b条件。
查看ICP状态:
sql复制SHOW VARIABLES LIKE 'optimizer_switch';
8.2 多范围读取优化(MRR)
MRR优化对范围查询特别有效:
sql复制-- 假设有索引 (a)
SELECT * FROM table WHERE a BETWEEN 10 AND 20;
传统方式是按索引顺序读取并频繁随机IO。MRR先收集所有行ID,然后按主键顺序读取,减少随机IO。
启用MRR:
sql复制SET optimizer_switch='mrr=on,mrr_cost_based=off';
8.3 索引合并优化
当查询条件涉及多个索引时,MySQL可能使用索引合并:
sql复制-- 假设有索引idx_a(a)和idx_b(b)
SELECT * FROM table WHERE a = 1 OR b = 2;
通过EXPLAIN的Extra列可以看到"Using union(idx_a,idx_b)"表示使用了索引合并。
8.4 不可见索引测试
MySQL 8.0+支持将索引标记为不可见,用于测试删除索引的影响:
sql复制-- 将索引设为不可见
ALTER TABLE users ALTER INDEX idx_email INVISIBLE;
-- 测试查询性能后,可以删除或恢复
ALTER TABLE users ALTER INDEX idx_email VISIBLE;
