SQL 查询慢?先从 EXPLAIN 看起
目录
什么是 EXPLAIN?
EXPLAIN 输出字段详解
1. type:访问类型
2. key:使用的索引
3. rows:估计要扫描的行数
结合实例,用 EXPLAIN 优化多表关联查询
总结
在日常开发中,我们经常会遇到 SQL 查询变慢的情况。明明只是简单的查询,却要等上好几秒,严重影响了应用的性能。这时,很多人会下意识地去调整索引、修改 SQL 语句,但往往效果不佳。其实,想要优化 SQL 查询,首先要做的是了解查询的执行过程,而EXPLAIN 命令就是我们的 “透视镜”,它能帮我们看清 SQL 语句的执行计划,找到性能瓶颈所在。
什么是 EXPLAIN?
EXPLAIN 是 MySQL 等关系型数据库提供的一个非常实用的命令,它可以模拟优化器执行 SQL 查询语句,从而让我们知道 MySQL 是如何处理 SQL 语句的。通过分析 EXPLAIN 输出的执行计划,我们可以了解到表的读取顺序、数据读取操作的类型、哪些索引被使用、表之间的连接方式等关键信息,进而针对性地进行优化。
使用 EXPLAIN 非常简单,只需在要执行的 SQL 语句前加上 EXPLAIN 关键字即可,例如:
EXPLAIN SELECT * FROM student WHERE age > 20;
执行上述命令后,MySQL 会返回一张包含多个字段的表格,每个字段都代表着执行计划的不同信息。
EXPLAIN 输出字段详解
EXPLAIN 输出的字段有很多,其中type、key、rows这三个字段尤为重要,它们能帮助我们快速判断查询语句的性能瓶颈。
1. type:访问类型
type 字段表示 MySQL 在表中找到所需行的方式,也就是访问类型。它的取值从好到坏依次是:
- system:表中只有一行数据(系统表),这是 const 类型的特例,性能最好。
- const:表示通过索引一次就找到了,常用于主键或唯一索引查询。例如,根据主键查询某一行数据,type 就是 const。
- eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于多表关联查询中,使用主键或唯一索引作为关联条件。
- ref:非唯一性索引扫描,返回匹配某个单独值的所有行。例如,使用普通索引查询满足条件的行。
- range:只检索给定范围的行,使用索引来选择行。例如,使用 between、in 等关键字的查询。
- index:全索引扫描,遍历整个索引来获取数据。虽然比全表扫描快,但性能仍然较差。
- ALL:全表扫描,遍历整个表来找到匹配的行,性能最差。
在实际开发中,我们要尽量避免出现 ALL 和 index 类型的访问,争取达到 ref 或更好的类型。
2. key:使用的索引
key 字段表示 MySQL 实际使用的索引。如果该字段为 NULL,则表示没有使用索引。这时候我们就要思考,是不是没有为查询条件中的字段建立索引,或者索引建立得不合理。
例如,在查询 student 表中 age 大于 20 的学生时,如果 age 字段没有建立索引,key 字段就会为 NULL,查询会进行全表扫描;如果建立了索引,key 字段就会显示该索引的名称。
3. rows:估计要扫描的行数
rows 字段表示 MySQL 估计要执行查询时必须检查的行数。这个值越小,说明查询效率越高。需要注意的是,rows 是一个估计值,并不是实际值,但它能大致反映出查询的性能。
如果 rows 的值很大,说明查询可能需要扫描大量的数据,这时候就需要考虑优化索引或修改查询语句了。
除了上述三个关键字段外,EXPLAIN 输出的其他字段也有一定的参考价值,例如:
- id:表示查询中操作表的顺序,id 相同则执行顺序由上至下,id 不同则 id 值越大优先级越高。
- select_type:表示查询的类型,如简单查询、联合查询、子查询等。
- table:表示当前行正在访问的表的名称。
- possible_keys:表示可能使用的索引,但不一定会被实际使用。
- Extra:包含了很多额外的信息,如是否使用了临时表、是否进行了文件排序等,这些信息能帮助我们进一步分析查询性能。
结合实例,用 EXPLAIN 优化多表关联查询
下面我们结合学生表(student)和班级表(class)的例子,展示如何用 EXPLAIN 优化多表关联查询。
表结构及数据
学生表(student)结构:
CREATE TABLE student (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(20) NOT NULL,age INT,class_id INT,INDEX idx_class_id (class_id)
);
班级表(class)结构:
CREATE TABLE class (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(20) NOT NULL
);
假设 student 表中有 10000 条数据,class 表中有 100 条数据。
未优化的多表关联查询
现在我们需要查询所有年龄大于 20 的学生及其所在班级的名称,SQL 语句如下:
SELECT s.name, c.name
FROM student s
JOIN class c ON s.class_id = c.id
WHERE s.age > 20;
我们用 EXPLAIN 来分析一下这个查询:
EXPLAIN
SELECT s.name, c.name
FROM student s
JOIN class c ON s.class_id = c.id
WHERE s.age > 20;
执行结果可能如下(不同数据库环境可能会有差异):
id | select_type | table |
1 | SIMPLE | s |
1 | SIMPLE | c |
从执行计划中可以看出:
- student 表的 type 是 ALL,说明进行了全表扫描,rows 为 10000,需要扫描大量数据。
- 虽然 student 表有 idx_class_id 索引,但在这个查询中没有被使用(key 为 NULL),因为查询条件是 age > 20,而 age 字段没有建立索引。
优化查询
针对上述问题,我们可以为 student 表的 age 字段建立索引:
CREATE INDEX idx_age ON student (age);
然后再用 EXPLAIN 分析查询:
EXPLAIN
SELECT s.name, c.name
FROM student s
JOIN class c ON s.class_id = c.id
WHERE s.age > 20;
执行结果可能如下:
id | select_type | table |
1 | SIMPLE | s |
1 | SIMPLE | c |
可以看到,优化后 student 表的 type 变为了 range,使用了 idx_age 索引(key 为 idx_age),rows 减少到了 2000,查询效率有了明显提升。
这是因为建立 age 索引后,MySQL 可以通过索引快速定位到 age > 20 的学生记录,而不需要扫描整个表。
总结
EXPLAIN 命令是优化 SQL 查询的有力工具,通过分析它输出的执行计划,特别是 type、key、rows 等关键字段,我们可以快速找到查询语句的性能瓶颈。在进行多表关联查询时,合理地建立索引是提高查询效率的关键。
当然,EXPLAIN 的使用还有很多细节需要我们去掌握,在实际开发中,我们要多动手实践,不断积累经验,才能写出高效的 SQL 语句。希望本文能帮助大家更好地理解和使用 EXPLAIN 命令,让 SQL 查询不再 “慢” 下来。