-- 可以使用索引的查询 SELECT*FROMuserWHERE name ='John'; -- ✅ 使用索引 SELECT*FROMuserWHERE name ='John'AND age =25; -- ✅ 使用索引 SELECT*FROMuserWHERE name ='John'AND age =25AND email ='john@example.com'; -- ✅ 使用索引 SELECT*FROMuserWHERE name ='John'AND email ='john@example.com'; -- ✅ 使用索引(部分使用)
-- 不能使用索引的查询 SELECT*FROMuserWHERE age =25; -- ❌ 不包含最左列 name SELECT*FROMuserWHERE email ='john@example.com'; -- ❌ 不包含最左列 name SELECT*FROMuserWHERE age =25AND email ='john@example.com'; -- ❌ 不包含最左列 name
9.3 索引列顺序选择
组合索引的列顺序很重要,应该遵循以下原则:
区分度高的列在前:选择性高的列放在前面
等值查询在前:等值查询的列放在范围查询的列前面
查询频率高的列在前:经常使用的列放在前面
排序字段考虑:如果经常需要排序,考虑将排序字段加入索引
9.4 示例
1 2 3 4 5 6 7 8 9
-- 好的组合索引设计 -- 假设 name 区分度高,age 用于范围查询,email 用于等值查询 CREATE INDEX idx_name_email_age ONuser(name, email, age);
-- 查询示例 SELECT*FROMuser WHERE name ='John' AND email ='john@example.com' AND age >25; -- 可以使用索引
10. 索引优化
10.1 索引设计原则
选择性高的列:区分度高的列适合建索引
频繁查询的列:WHERE、JOIN、ORDER BY 中经常使用的列
避免过多索引:索引过多会影响写性能
小字段优先:索引字段应该尽可能小
NOT NULL 优先:NULL 值会增加索引复杂度
10.2 不适合建索引的场景
区分度低的列:如性别、状态等只有几个值的列
频繁更新的列:更新会触发索引维护
很少查询的列:不经常使用的列不需要索引
大文本字段:TEXT、BLOB 等大字段不适合建索引(可以使用前缀索引)
10.3 索引失效场景
函数操作:对索引列使用函数
1 2
SELECT*FROMuserWHEREUPPER(name) ='JOHN'; -- 索引失效 SELECT*FROMuserWHERE name =UPPER('john'); -- 可以使用索引
类型转换:隐式类型转换
1 2 3
-- name 是 VARCHAR 类型 SELECT*FROMuserWHERE name =123; -- 索引失效(类型转换) SELECT*FROMuserWHERE name ='123'; -- 可以使用索引
前导模糊查询:LIKE 以 % 开头
1 2
SELECT*FROMuserWHERE name LIKE'%John'; -- 索引失效 SELECT*FROMuserWHERE name LIKE'John%'; -- 可以使用索引
OR 条件:OR 连接的条件如果有一个没有索引
1 2
-- name 有索引,age 没有索引 SELECT*FROMuserWHERE name ='John'OR age =25; -- 索引失效
NOT、!=、<>:不等于操作
1
SELECT*FROMuserWHERE name !='John'; -- 索引失效
IS NULL / IS NOT NULL:在某些情况下可能失效
1
SELECT*FROMuserWHERE name ISNULL; -- 可能失效
10.4 索引使用分析
1 2 3 4 5 6 7 8
-- 使用 EXPLAIN 分析查询计划 EXPLAIN SELECT*FROMuserWHERE name ='John';
-- 关键字段说明: -- type: 访问类型(const > eq_ref > ref > range > index > ALL) -- key: 使用的索引 -- rows: 扫描的行数 -- Extra: 额外信息(Using index 表示覆盖索引)
11. 前缀索引
11.1 定义
前缀索引是指只对字符串的前几个字符建立索引,而不是整个字符串。
11.2 使用场景
长字符串字段:如 URL、邮箱、地址等
节省存储空间:减少索引大小
提高查询效率:在某些场景下前缀索引已经足够
11.3 前缀长度选择
区分度:前缀长度应该保证足够的区分度
平衡:在区分度和索引大小之间平衡
11.4 示例
1 2 3 4 5 6 7 8 9
-- 创建前缀索引,只索引前 10 个字符 CREATE INDEX idx_email_prefix ONuser(email(10));
-- 计算不同前缀长度的区分度 SELECT COUNT(DISTINCTLEFT(email, 5)) /COUNT(*) AS prefix5, COUNT(DISTINCTLEFT(email, 10)) /COUNT(*) AS prefix10, COUNT(DISTINCTLEFT(email, 20)) /COUNT(*) AS prefix20 FROMuser;
12. 索引维护
12.1 查看索引
1 2 3 4 5 6 7 8 9 10
-- 查看表的所有索引 SHOW INDEX FROMuser;
-- 查看索引统计信息 SHOW INDEX FROMuserWHERE Key_name ='idx_name';
-- 查看索引使用情况 SELECT*FROM information_schema.STATISTICS WHERE TABLE_SCHEMA ='database_name' AND TABLE_NAME ='user';
12.2 重建索引
1 2 3 4 5 6
-- 重建索引(InnoDB) ALTER TABLEuserDROP INDEX idx_name; ALTER TABLEuserADD INDEX idx_name (name);
-- 查看索引使用统计(需要开启 performance_schema) SELECT OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME, COUNT_FETCH, COUNT_INSERT, COUNT_UPDATE, COUNT_DELETE FROM performance_schema.table_io_waits_summary_by_index_usage WHERE OBJECT_SCHEMA ='database_name' AND OBJECT_NAME ='user';
13.2 识别未使用的索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14
-- 查找可能未使用的索引 SELECT s.TABLE_SCHEMA, s.TABLE_NAME, s.INDEX_NAME, s.CARDINALITY FROM information_schema.STATISTICS s LEFTJOIN performance_schema.table_io_waits_summary_by_index_usage p ON s.TABLE_SCHEMA = p.OBJECT_SCHEMA AND s.TABLE_NAME = p.OBJECT_NAME AND s.INDEX_NAME = p.INDEX_NAME WHERE s.TABLE_SCHEMA ='database_name' AND p.INDEX_NAME ISNULL AND s.INDEX_NAME !='PRIMARY';