1. MySQL索引基础认知
第一次接触MySQL索引时,我误以为它就是个简单的"目录"功能。直到某次处理百万级数据查询超时问题,才真正理解索引的价值所在。索引本质上是一种特殊的数据结构,它通过预先排序和存储关键字段的引用,使数据库引擎能够快速定位数据位置,避免全表扫描。
重要提示:在没有索引的情况下执行
SELECT * FROM large_table WHERE id=1000,MySQL需要逐行检查整个表,而建立索引后查询时间可以从秒级降到毫秒级。
索引的工作原理类似于书籍目录:
- 数据表相当于整本书的内容
- 索引相当于按字母排序的目录页
- 数据库引擎先查目录找到页码,再直接翻到对应页面
MySQL支持多种索引类型,每种都有特定的使用场景:
| 索引类型 | 特点描述 | 适用场景 |
|---|---|---|
| B-Tree索引 | 默认索引类型,支持=、>、>=、<、<=、BETWEEN、IN等操作 | 绝大多数常规查询场景 |
| 哈希索引 | 基于哈希表实现,只能用于等值比较查询 | 精确匹配且不排序的场景 |
| 全文索引 | 专门用于文本内容的搜索 | 文章内容搜索、关键词检索 |
| 空间索引(R-Tree) | 用于地理空间数据类型 | 地图应用、位置服务 |
| 复合索引 | 多个列组合建立的索引 | 多条件联合查询 |
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 索引创建与维护实操
2.1 创建索引的正确姿势
创建索引不是简单地加个INDEX关键字那么简单,需要综合考虑字段选择、索引类型和表结构。以下是我在项目中总结的最佳实践:
sql复制-- 基本单列索引
CREATE INDEX idx_user_name ON users(username);
-- 唯一索引(避免重复值)
CREATE UNIQUE INDEX idx_user_email ON users(email);
-- 复合索引(注意字段顺序)
CREATE INDEX idx_order_status_date ON orders(status, create_date);
-- 全文索引(支持文本搜索)
CREATE FULLTEXT INDEX idx_product_desc ON products(description);
复合索引的字段顺序至关重要,必须遵循"最左前缀原则"。比如上面的idx_order_status_date索引:
- 有效查询:
WHERE status='paid'、WHERE status='paid' AND create_date>'2023-01-01' - 无效查询:
WHERE create_date>'2023-01-01'(跳过了status字段)
2.2 索引维护与优化
索引不是一劳永逸的,需要定期维护。我常用的维护策略包括:
- 重建碎片化索引(每月一次)
sql复制ALTER TABLE orders REBUILD INDEX idx_order_status_date;
- 监控索引使用情况
sql复制-- 查看未使用的索引
SELECT * FROM sys.schema_unused_indexes;
-- 查看索引统计信息
SHOW INDEX FROM orders;
- 定期分析表(更新统计信息)
sql复制ANALYZE TABLE orders;
血泪教训:曾经有个表积累了30%的索引碎片,导致查询性能下降60%。现在我会在维护窗口定期执行
OPTIMIZE TABLE。
3. 索引性能分析与调优
3.1 EXPLAIN深度解析
掌握EXPLAIN是优化查询的必修课。来看个实际案例:
sql复制EXPLAIN SELECT * FROM orders
WHERE user_id=100 AND status='shipped'
ORDER BY create_date DESC LIMIT 10;
输出结果的关键字段解读:
| 字段 | 值 | 含义 |
|---|---|---|
| type | ref | 索引查找类型,all最差,const最优 |
| possible_keys | idx_user,idx_status | 可能使用的索引 |
| key | idx_user | 实际使用的索引 |
| rows | 500 | 预估检查的行数 |
| Extra | Using filesort | 需要额外排序,可能影响性能 |
当发现Using filesort或Using temporary时,就需要考虑调整索引了。针对上面的查询,应该建立复合索引:
sql复制CREATE INDEX idx_user_status_date ON orders(user_id, status, create_date);
3.2 索引失效的常见陷阱
即使建立了索引,这些情况也会导致索引失效:
- 隐式类型转换
sql复制-- user_id是整数类型,但用字符串查询
SELECT * FROM users WHERE user_id='100'; -- 索引失效
- 使用函数操作
sql复制SELECT * FROM orders WHERE DATE(create_date)='2023-01-01'; -- 索引失效
- 前导通配符LIKE
sql复制SELECT * FROM products WHERE name LIKE '%apple%'; -- 无法使用索引
- OR条件不当使用
sql复制SELECT * FROM orders WHERE status='shipped' OR total_amount>1000; -- 可能全表扫描
解决方案:对于OR条件,可以改用UNION ALL:
sql复制SELECT * FROM orders WHERE status='shipped'
UNION ALL
SELECT * FROM orders WHERE total_amount>1000 AND status!='shipped';
4. 高级索引策略与实战技巧
4.1 覆盖索引优化
覆盖索引是指查询所需的所有列都包含在索引中,无需回表查数据。这是我优化查询的杀手锏:
sql复制-- 普通查询(需要回表)
SELECT user_id, username, email FROM users WHERE username LIKE 'john%';
-- [优化方案](https://taotoken.net?utm_source=general):创建包含所有需要字段的索引
CREATE INDEX idx_user_cover ON users(username, user_id, email);
-- 现在查询只需要扫描索引
EXPLAIN SELECT user_id, username, email FROM users WHERE username LIKE 'john%';
-- 输出Extra列会显示"Using index"
4.2 索引下推技术
MySQL 5.6引入的索引下推(ICP)可以在存储引擎层提前过滤数据:
sql复制-- 表结构
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
status VARCHAR(20),
create_date DATETIME,
INDEX idx_user_status (user_id, status)
);
-- 没有ICP时执行流程:
-- 1. 通过user_id=100定位索引记录
-- 2. 回表读取完整记录
-- 3. 在server层检查status='shipped'
-- 启用ICP后:
-- 1. 通过user_id=100定位索引记录
-- 2. 在存储引擎层直接检查status='shipped'
-- 3. 只对符合条件的记录回表
可以通过变量控制ICP:
sql复制SET optimizer_switch='index_condition_pushdown=on';
4.3 索引跳跃扫描
MySQL 8.0新增的索引跳跃扫描特性,即使不满足最左前缀也能利用索引:
sql复制-- 复合索引 (gender, age)
CREATE INDEX idx_gender_age ON employees(gender, age);
-- 8.0之前:无法使用索引
SELECT * FROM employees WHERE age > 30;
-- 8.0之后:优化器会自动"跳过"gender字段
-- 相当于执行了:
-- SELECT * FROM employees WHERE gender='M' AND age > 30
-- UNION ALL
-- SELECT * FROM employees WHERE gender='F' AND age > 30
5. 生产环境索引管理规范
5.1 索引设计原则
经过多年踩坑,我总结出这些黄金法则:
-
选择性原则:优先为高选择性字段建索引(如用户ID比性别更适合)
sql复制-- 计算字段选择性 SELECT COUNT(DISTINCT status)/COUNT(*) FROM orders; -
适度原则:单表索引不超过5个,避免写入性能下降
-
短索引原则:对长字符串使用前缀索引
sql复制CREATE INDEX idx_product_name ON products(name(20)); -
避免冗余:删除功能重复的索引
sql复制-- idx_a和idx_a_b存在冗余 CREATE INDEX idx_a ON t(a); CREATE INDEX idx_a_b ON t(a,b); -- 可以删除idx_a
5.2 索引监控脚本
这是我常用的索引监控脚本,保存为monitor_indexes.sql:
sql复制SELECT
t.TABLE_SCHEMA,
t.TABLE_NAME,
t.TABLE_ROWS,
i.INDEX_NAME,
i.CARDINALITY,
ROUND(i.CARDINALITY/t.TABLE_ROWS*100,2) AS selectivity,
s.INDEX_LENGTH/1024/1024 AS index_size_mb
FROM
INFORMATION_SCHEMA.TABLES t
JOIN
INFORMATION_SCHEMA.STATISTICS i ON t.TABLE_SCHEMA=i.TABLE_SCHEMA
AND t.TABLE_NAME=i.TABLE_NAME
JOIN
INFORMATION_SCHEMA.INNODB_SYS_TABLESTATS s ON t.TABLE_NAME=s.NAME
WHERE
t.TABLE_SCHEMA NOT IN ('mysql','information_schema','performance_schema')
AND t.TABLE_ROWS > 1000
ORDER BY
selectivity DESC, t.TABLE_ROWS DESC;
5.3 索引变更管理流程
在生产环境修改索引必须遵循严格流程:
- 先在测试环境验证性能影响
- 使用pt-online-schema-change工具在线变更
- 低峰期执行,并设置超时回滚
- 变更后立即监控QPS和慢查询
- 保留回滚SQL(记录原索引定义)
bash复制# 使用pt工具添加索引示例
pt-online-schema-change \
--alter="ADD INDEX idx_new (col1,col2)" \
D=database,t=table \
--execute
曾经有次直接在高峰期执行ALTER TABLE导致服务不可用,现在所有索引变更都必须走审批流程。
