前言
上篇文章给大家介绍了数据完整性约束
这篇文章给大家深入解析索引原理与优化策略
在掌握了数据完整性约束后,我们将探索MySQL性能优化的核心武器——索引。本文将从底层原理到实战优化,全面解析索引工作机制,帮助你的查询性能提升10倍以上。
一、索引:数据库的高速公路系统
索引的本质是有序数据结构,它如同书籍的目录,让数据库引擎能快速定位数据位置。对比无索引的全表扫描,索引带来的性能提升是指数级的:
-- 无索引查询(耗时1.8秒)
SELECT * FROM orders WHERE user_id = 10086;
-- 添加索引后(耗时0.002秒)
CREATE INDEX idx_user ON orders(user_id);
为什么需要索引?
场景 | 无索引耗时 | 有索引耗时 | 提升倍数 |
---|---|---|---|
10万行单条查询 | 120ms | 2ms | 60倍 |
100万范围查询 | 2.1s | 35ms | 60倍 |
500万表连接 | >30s | 0.9s | 33倍 |
二、索引底层数据结构解析
1. B+树:MySQL索引的基石
B+树是InnoDB默认索引结构,其核心优势在于:
-
多叉树结构:降低树高度(3层可存2000万数据)
-
数据全在叶子节点:非叶节点仅存键值
-
双向链表连接叶子节点:高效范围查询
graph TD
A[根节点<br/>P1 P2 P3] --> B[非叶节点<br/>P1 P2]
A --> C[非叶节点<br/>P3 P4]
B --> D[叶子节点<br/>1-100]
B --> E[叶子节点<br/>101-200]
C --> F[叶子节点<br/>201-300]
D -->|双向链表| E
E -->|双向链表| F
2. 哈希索引:精准匹配利器
Memory引擎默认索引,适合等值查询:
-- 创建哈希索引
CREATE TABLE sessions (
session_id CHAR(32) PRIMARY KEY,
user_id INT,
INDEX idx_user USING HASH (user_id)
) ENGINE=MEMORY;
特点:
-
O(1)时间复杂度查找
-
不支持范围查询
-
存在哈希冲突问题
3. 全文索引:文本搜索引擎
MyISAM/InnoDB支持,解决LIKE'%keyword%'低效问题:
ALTER TABLE articles
ADD FULLTEXT INDEX ft_content (title, content);
-- 自然语言搜索
SELECT * FROM articles
WHERE MATCH(title,content) AGAINST('数据库优化');
三、MySQL索引类型全景图
1. 按数据结构分类
索引类型 | 支持引擎 | 是否有序 | 适用场景 |
---|---|---|---|
B+Tree | InnoDB/MyISAM | 是 | 90%以上的场景 |
Hash | Memory/NDB | 否 | 等值查询缓存表 |
R-Tree | MyISAM | 是 | 地理空间数据 |
Full-Text | InnoDB/MyISAM | 否 | 文本内容搜索 |
2. 按逻辑功能分类
① 主键索引(Clustered Index)
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT, -- 主键索引
name VARCHAR(50)
);
特点:叶子节点存储整行数据
② 辅助索引(Secondary Index)
CREATE INDEX idx_email ON users(email);
特点:叶子节点存储主键值(需回表查询)
③ 覆盖索引(Covering Index)
-- 创建覆盖索引
CREATE INDEX idx_user_order ON orders(user_id, order_date, amount);
-- 避免回表
SELECT user_id, order_date, amount
FROM orders WHERE user_id = 1001; -- 直接使用索引数据
④ 联合索引(Composite Index)
CREATE INDEX idx_name_phone ON customers(last_name, first_name, phone);
最左前缀原则:
-
有效:
WHERE last_name='张'
-
有效:
WHERE last_name='张' AND first_name='三'
-
无效:
WHERE first_name='三'
(跳过左列)
四、索引操作全指南
1. 创建索引
-- 单列索引
CREATE INDEX idx_created ON orders(created_at);
-- 多列索引
CREATE INDEX idx_user_status ON orders(user_id, status);
-- 唯一索引
CREATE UNIQUE INDEX uid_email ON users(email);
-- 前缀索引(文本字段)
CREATE INDEX idx_comment ON reviews(comment(20));
2. 查看索引
SHOW INDEX FROM orders;
+-------+------------+-----------------+--------------+
| Table | Non_unique | Key_name | Column_name |
+-------+------------+-----------------+--------------+
| orders| 0 | PRIMARY | id |
| orders| 1 | idx_user_status | user_id |
| orders| 1 | idx_user_status | status |
+-------+------------+-----------------+--------------+
3. 删除索引
ALTER TABLE orders DROP INDEX idx_created;
4. 索引重建(优化碎片)
-- InnoDB引擎
ALTER TABLE orders ENGINE=InnoDB;
-- MyISAM引擎
REPAIR TABLE orders QUICK;
五、索引优化实战技巧
1. EXPLAIN执行计划分析
EXPLAIN SELECT * FROM products
WHERE category='electronics' AND price > 1000;
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | products | ref | idx_category | idx_category | 102 | const| 1256 | Using where |
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
关键字段解读:
-
type:const > ref > range > index > ALL(性能递减)
-
key_len:索引使用长度(联合索引是否完整使用)
-
rows:扫描行数(越小越好)
-
Extra:
-
Using index
:覆盖索引 -
Using filesort
:需要额外排序 -
Using temporary
:使用临时表
-
2. 避免索引失效的八大场景
-
隐式类型转换:
WHERE phone=13800138000
(phone是字符串) -
函数操作列:
WHERE YEAR(create_time)=2023
-
前导通配符:
WHERE name LIKE '%John'
-
OR条件不当:
WHERE a=1 OR b=2
(a、b需分别有索引) -
跳过联合索引左列:
INDEX(a,b,c)
→WHERE b=1 AND c=2
-
!= / <> 操作符:
WHERE status != 1
-
索引列参与运算:
WHERE price+100 > 2000
-
范围查询放联合索引首位:
INDEX(a,b)
→WHERE a>100 AND b=1
3. 高级优化策略
① 索引下推(ICP)
MySQL 5.6+引入,在存储引擎层过滤数据:
-- 联合索引 (city, age)
SELECT * FROM users
WHERE city='北京' AND age > 30; -- ICP直接过滤age
② MRR多范围读取
优化磁盘随机访问为顺序访问:
SET optimizer_switch='mrr=on';
③ 索引跳跃扫描
MySQL 8.0+支持,跳过联合索引前缀:
-- 索引 (gender, name)
SELECT * FROM persons
WHERE name = '张三'; -- 可分别扫描gender=M/F分区
六、索引设计案例
1. 设计原则
-
选择高区分度列:性别(50%) vs 手机号(99.99%)
-
频繁查询条件优先:WHERE和ORDER BY列
-
短索引优于长索引:INT(4B) vs CHAR(100)
-
更新频繁的表谨慎建索引
2. 电商平台索引设计案例
-- 商品表
CREATE TABLE products (
id BIGINT PRIMARY KEY,
category_id INT NOT NULL,
brand_id INT NOT NULL,
price DECIMAL(10,2) NOT NULL,
status TINYINT NOT NULL, -- 0下架 1上架
INDEX idx_cat_brand (category_id, brand_id),
INDEX idx_price_status (price, status)
);
-- 订单表
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
status ENUM('unpaid','paid','shipped') NOT NULL,
amount DECIMAL(12,2) NOT NULL,
created_at DATETIME NOT NULL,
INDEX idx_user_status (user_id, status),
INDEX idx_created (created_at)
);
-- 订单明细表
CREATE TABLE order_items (
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
PRIMARY KEY (order_id, product_id), -- 联合主键
INDEX idx_product (product_id)
);
3. 索引监控与维护
-- 查看索引使用统计
SELECT *
FROM sys.schema_index_statistics
WHERE table_name = 'orders';
-- 检测未使用索引
SELECT *
FROM sys.schema_unused_indexes;
-- 重建碎片化索引
ALTER TABLE orders REBUILD PARTITION ALL;
七、索引的代价与规避策略
索引不是免费的:
-
写成本增加:每次INSERT/UPDATE需维护索引
-
存储空间占用:索引通常占数据量20%-50%
-
优化器选择困难:过多索引导致执行计划不稳定
规避策略:
-
读写分离:只在写库建必要索引
-
延迟索引创建:数据迁移后建索引
-
索引压缩:适用于文本索
CREATE INDEX idx_content ON articles (content)
WITH PARSER ngram
COMMENT 'INDEX_COLUMN=content COMPRESSION=zlib';
-
分区表局部索引:只对热点分区建索引
八、总结与最佳实践
索引优化黄金法则:
-
理解业务查询模式(80%优化在于设计)
-
优先使用EXPLAIN分析
-
联合索引遵循最左前缀原则
-
避免过度索引(单表建议不超过5个)
-
定期监控索引使用率
不同数据量的索引策略:
数据规模 | 索引策略 | 注意事项 |
---|---|---|
<10万行 | 主键索引+必要查询索引 | 避免过早优化 |
10-100万 | 联合索引+覆盖索引 | 关注范围查询性能 |
>100万 | 分区索引+索引下推+MRR | 定期重建碎片化索引 |