各位观众老爷,晚上好!我是你们的老朋友,今天咱们来聊聊MySQL索引这玩意儿,保证让你听完之后,索引玩得比我还溜!
今天的主题是:MySQL编程进阶之:索引设计的最佳实践:如何为WHERE
、ORDER BY
和GROUP BY
子句创建索引。
说白了,索引就是为了让数据库查询更快。想象一下,你在一本没有目录的超大字典里找一个词,是不是得一页一页翻?有了目录(索引),直接定位到那一页,效率瞬间提升几个数量级!
一、 索引的种类和基本概念(快速过一遍)
在深入WHERE
、ORDER BY
和GROUP BY
之前,咱们先简单回顾一下索引的类型,免得大家一脸懵逼。
-
B-Tree 索引: 这是MySQL里最常见的索引类型,也是默认类型。它可以加速数据的查找,支持范围查询,还可以在ORDER BY和GROUP BY操作中使用。
-
Hash 索引: Hash索引非常快,但它有一些限制。它只适用于等值查询(
=
,IN
,<=>
),不支持范围查询,也不能用于ORDER BY 和 GROUP BY。Memory存储引擎默认使用Hash索引。 -
Fulltext 索引: 全文索引用于在文本中搜索关键词。适用于MyISAM和InnoDB,但在处理大量文本时,性能不如专业的搜索引擎。
-
Spatial 索引: 空间索引用于地理空间数据的查询。
我们今天的重点是 B-Tree 索引,毕竟它是万金油,哪里都能用得上。
二、 WHERE 子句与索引
WHERE
子句是SQL查询中最常用的部分,也是索引发挥最大作用的地方。
1. 单列索引:
最简单的情况,就是为WHERE
子句中常用的单个列创建索引。
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
age INT,
city VARCHAR(50)
);
-- 为 username 列创建索引
CREATE INDEX idx_username ON users (username);
-- 查询 username 为 '张三' 的用户
SELECT * FROM users WHERE username = '张三';
在这个例子中,idx_username
索引会大大加速 WHERE username = '张三'
的查询。
注意事项:
- 索引列的选择: 选择区分度高的列作为索引列。区分度越高,索引效果越好。比如,
gender
列只有两个值(男/女),区分度很低,不适合作为索引列。而username
列,每个用户都有不同的用户名,区分度很高,适合作为索引列。 - 索引的维护成本: 索引会占用额外的存储空间,并且每次插入、更新、删除数据时,都需要维护索引。所以,不要创建过多的索引。
2. 联合索引 (复合索引):
当WHERE
子句中包含多个列时,可以使用联合索引。
-- 为 username 和 city 列创建联合索引
CREATE INDEX idx_username_city ON users (username, city);
-- 查询 username 为 '张三' 并且 city 为 '北京' 的用户
SELECT * FROM users WHERE username = '张三' AND city = '北京';
这个例子中,idx_username_city
索引会加速 WHERE username = '张三' AND city = '北京'
的查询。
最重要的原则:最左前缀原则
联合索引有一个非常重要的原则:最左前缀原则。 简单来说,就是查询条件必须包含索引的最左边的列,才能使用该索引。
- 可以使用索引的情况:
WHERE username = '张三'
WHERE username = '张三' AND city = '北京'
- 无法使用索引的情况:
WHERE city = '北京'
WHERE city = '北京' AND username = '张三'
(虽然查询条件包含了索引中的两列,但是顺序不对,索引仍然无法使用)
为什么要有最左前缀原则?
因为索引是按照索引列的顺序排序的。比如,idx_username_city
索引,首先按照 username
排序,然后在 username
相同的情况下,按照 city
排序。 如果查询条件没有 username
,就无法利用索引的排序。
如何选择联合索引的列顺序?
- 区分度高的列放在前面: 区分度高的列,能够更快地缩小查询范围。
- 经常使用的列放在前面: 经常使用的列,能够提高索引的利用率。
比如,username
列的区分度比 city
列高,并且 username
列经常被用于查询,那么就应该将 username
列放在联合索引的最前面。
3. 范围查询:
B-Tree 索引支持范围查询,例如 >
、<
、>=
、<=
、BETWEEN
、LIKE
。
-- 查询年龄在 18 到 25 之间的用户
SELECT * FROM users WHERE age BETWEEN 18 AND 25;
-- 查询 username 以 '张' 开头的用户
SELECT * FROM users WHERE username LIKE '张%';
注意事项:
LIKE
查询:LIKE '张%'
可以使用索引,但LIKE '%张'
无法使用索引。 因为LIKE '张%'
可以利用索引的前缀匹配,而LIKE '%张'
无法利用索引的前缀匹配。- 范围查询之后的列无法使用索引: 如果联合索引中,某个列使用了范围查询,那么该列之后的列无法使用索引。
例如,有一个联合索引 idx_age_city
(age, city),如果查询条件是 WHERE age > 20 AND city = '北京'
,那么只能使用 age
列的索引,无法使用 city
列的索引。
4. 函数和表达式:
如果在WHERE
子句中使用了函数或表达式,索引通常无法使用。
-- 无法使用索引
SELECT * FROM users WHERE YEAR(birthday) = 1990;
-- 无法使用索引
SELECT * FROM users WHERE age + 10 > 30;
解决方法:
- 尽量避免在
WHERE
子句中使用函数和表达式。 - 可以创建一个计算列,并为该列创建索引。 (但这种方法会增加数据维护的复杂性)
三、 ORDER BY 子句与索引
ORDER BY
子句用于对查询结果进行排序。 如果没有索引,MySQL需要进行文件排序 (filesort),效率很低。
1. 使用索引排序:
如果ORDER BY
子句中的列与索引的列顺序一致,并且排序方式一致(都是升序或都是降序),就可以使用索引进行排序,避免文件排序。
-- 使用 idx_username 索引排序
SELECT * FROM users ORDER BY username;
-- 使用 idx_username_city 索引排序
SELECT * FROM users ORDER BY username, city;
2. 无法使用索引排序的情况:
- 排序顺序不一致: 如果
ORDER BY
子句中同时包含升序和降序,索引无法使用。-- 无法使用索引排序 SELECT * FROM users ORDER BY username ASC, city DESC;
-
索引列不连续: 如果
ORDER BY
子句中的列不是索引的最左前缀,或者中间缺少索引列,索引无法使用。-- 无法使用索引排序 SELECT * FROM users ORDER BY city; -- 无法使用索引排序 SELECT * FROM users WHERE username = '张三' ORDER BY city, id; (id不在索引中)
- 使用了
WHERE
子句,但WHERE
子句中的列不是索引的最左前缀:-- 无法使用索引排序 SELECT * FROM users WHERE city = '北京' ORDER BY username;
3. WHERE
+ ORDER BY
:
如果WHERE
子句和ORDER BY
子句都使用了索引,可以提高查询效率。
-- 使用 idx_username_city 索引
SELECT * FROM users WHERE username = '张三' ORDER BY city;
这个例子中,WHERE username = '张三'
使用索引查找 username
为 ‘张三’ 的用户,然后 ORDER BY city
使用索引对结果按照 city
排序。
四、 GROUP BY 子句与索引
GROUP BY
子句用于对查询结果进行分组。 与 ORDER BY
类似,如果没有索引,MySQL需要进行文件排序。
1. 使用索引分组:
如果GROUP BY
子句中的列与索引的列顺序一致,就可以使用索引进行分组,避免文件排序。
-- 使用 idx_username 索引分组
SELECT COUNT(*) FROM users GROUP BY username;
-- 使用 idx_username_city 索引分组
SELECT COUNT(*) FROM users GROUP BY username, city;
2. 无法使用索引分组的情况:
与 ORDER BY
类似,以下情况无法使用索引分组:
- 索引列不连续: 如果
GROUP BY
子句中的列不是索引的最左前缀,或者中间缺少索引列,索引无法使用。 - 使用了
WHERE
子句,但WHERE
子句中的列不是索引的最左前缀:
3. WHERE
+ GROUP BY
:
如果WHERE
子句和GROUP BY
子句都使用了索引,可以提高查询效率。
-- 使用 idx_username_city 索引
SELECT COUNT(*) FROM users WHERE username LIKE '张%' GROUP BY city;
这个例子中,WHERE username LIKE '张%'
使用索引查找 username
以 ‘张’ 开头的用户,然后 GROUP BY city
使用索引对结果按照 city
分组。
五、 索引设计的实用技巧
- 不要过度索引: 过多的索引会增加存储空间,并且会降低插入、更新、删除数据的性能。
- 定期检查索引的使用情况: 可以使用
EXPLAIN
命令分析查询语句的执行计划,查看是否使用了索引。 -
考虑使用覆盖索引: 覆盖索引是指,查询语句需要的所有列都包含在索引中,这样可以避免回表查询,提高查询效率。
-- 假设只需要查询 username 和 city 列 CREATE INDEX idx_username_city_cover ON users (username, city); SELECT username, city FROM users WHERE username = '张三'; -- 直接从索引中获取数据,无需回表
- 长字符串列的前缀索引: 如果索引的列是长字符串,可以考虑使用前缀索引,只索引字符串的一部分。
-- 只索引 email 列的前 10 个字符 CREATE INDEX idx_email_prefix ON users (email(10));
注意事项: 前缀索引会降低索引的选择性,需要根据实际情况选择合适的前缀长度。
-
利用
EXPLAIN
命令分析你的 SQL 语句EXPLAIN SELECT * FROM users WHERE username = '张三';
EXPLAIN
会告诉你 MySQL 如何执行你的查询,包括是否使用了索引,使用了哪个索引,以及扫描了多少行数据。 认真阅读EXPLAIN
的结果,能够帮助你发现潜在的性能问题,并优化你的索引设计。 关键字段是type
和key
。type
最好是const
,eq_ref
,ref
,最差是ALL
。key
显示了实际使用的索引。
六、 常见问题与避坑指南
-
模糊查询的坑:
LIKE '%keyword%'
这种写法,索引直接失效!尽量避免,如果必须用,考虑全文索引或者搜索引擎。 -
隐式类型转换: 如果你查询的字段是字符串,但你给的值是数字,MySQL 可能会进行隐式类型转换,导致索引失效。
-- username 是字符串类型 SELECT * FROM users WHERE username = 123; -- 索引可能失效
-
OR 条件:
OR
连接的多个条件,如果只有一个条件有索引,索引可能不会被使用。 尽量使用UNION
或者改写 SQL。 -
数据量小的表: 数据量小的表,即使有索引,MySQL 也可能选择全表扫描,因为全表扫描可能比使用索引更快。
-
索引虽好,不要贪杯: 每个索引都会占用额外的存储空间,并且在数据修改时需要维护索引。过多的索引会降低写入性能。
七、总结
索引是MySQL性能优化的重要手段。 合理的索引设计可以大大提高查询效率。记住以下几点:
- 理解索引的类型和原理。
- 遵循最左前缀原则。
- 避免在
WHERE
子句中使用函数和表达式。 - 定期检查索引的使用情况。
- 不要过度索引。
好了,今天的讲座就到这里。希望大家能够学以致用,设计出高效的索引,让你的MySQL数据库跑得飞快! 感谢各位的观看,咱们下次再见!