在我们进行SQL查询优化的过程中,经常会用到mysql的explain命令。该命令是mysql提供给我们查看查询执行计划的工具,可以帮助我们分析查询的执行效率,找出问题所在。本文将以mysql explain为中心进行详细的阐述,从多个方面进行解释说明。
一、查询优化器
在执行SQL语句之前,mysql会先对SQL语句进行解析,并生成一颗查询解析树。得到查询解析树后,mysql会根据一些条件进行查询优化,包括索引、表连接顺序、子查询转换等,然后生成执行计划。在执行计划生成后,mysql会使用查询执行器对其进行执行,并返回结果。
在解析SQL语句的时候,mysql会尝试把查询转化为更高效的内部执行模型。mysql内部的执行模型是基于迭代器模型的,每个执行单元都是一个迭代器,可以将查询分为多个迭代器组合而成。优化器的主要作用是根据SQL语句的特点与限制条件,选择执行计划。
下面是一个简单的查询语句:
EXPLAIN SELECT id, name, age FROM users WHERE age > 18 ORDER BY age DESC, id ASC LIMIT 10;
当我们执行以上查询语句时,优化器会根据语句中的WHERE条件和ORDER BY子句,确定查询优化策略。可以通过explain命令查看执行计划:
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+------------------------------------+ | 1 | SIMPLE | users | NULL | ALL | NULL | NULL | NULL | NULL | 100000 | 10.00 | Using where; Using filesort; Range | +----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+------------------------------------+
其中的每一行都代表一个访问单元。
这里的select_type有三种可能的取值:
- SIMPLE
- PRIMARY
- SUBQUERY
以上查询语句的select_type为SIMPLE,表示这是一个简单的SELECT查询。下面来详细解释一下each字段的含义:
- id:查询标识符,表示查询的唯一标识符,通常是一个数字。如果该命令在同一个session中执行多次,每个查询的id都会不一样。
- select_type:查询类型,有以下几种取值:SIMPLE、PRIMARY、SUBQUERY等。
- table:查询的主表
- type:访问类型,代表了mysql在表中找到所需行的方式,常见的类型有:ALL、index、range等。
- possible_keys:可用索引,表示mysql能够使用哪些索引来优化查询。
- key:实际使用的索引,如果为NULL,则没有使用索引。
- key_len:索引长度
- ref:关联条件
- rows:扫描行数
- Extra:说明。
从上面的执行计划结果中可以看出,mysql并没有使用任何索引,而是进行了全表扫描,这是效率低下的。
二、WHERE子句的优化
在使用mysql explain分析查询计划时,WHERE子句往往是需要关注的重点,因为这决定了查询的过滤条件。
以下是一个示例,用于测试一个关于年龄的查询:
EXPLAIN SELECT * FROM users WHERE age > 30;
执行后的执行计划:
+----+-------------+-------+------------+------+---------------+------+---------+------+------+---------+-------------+ | ID | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered| Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+---------+-------------+ | 1 | SIMPLE | users | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Using where | +----+-------------+-------+------------+------+---------------+------+---------+------+------+---------+-------------+
结果中的type字段为ALL,意味着mysql需要扫描整张表来查找满足WHERE条件的数据。
为了提高效率,可以为某些字段创建索引,例如对于该用户表中的age字段来说,可能希望它拥有一个索引。以下是一个为age字段创建索引的示例SQL语句:
ALTER TABLE users ADD INDEX (age);
然后,重新执行查询命令:
EXPLAIN SELECT * FROM users WHERE age > 30;
执行后的查询计划如下:
+----+-------------+-------+------------+------+---------------+------+---------+------+------+---------+-------------+ | ID | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered| Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+---------+-------------+ | 1 | SIMPLE | users | NULL | ref | age | age | 5 | NULL | 14 | 100.00 | Using index | +----+-------------+-------+------------+------+---------------+------+---------+------+------+---------+-------------+
可以看出,当我们为age字段创建了一个索引时,查询优化器改变了type字段的值,并将key和ordering列的值设置为age。由于该索引使用了age字段,因此不再需要扫描全表,而是使用索引进行搜索,大大提高了查询效率。
三、JOIN查询优化
在mysql中,join操作通常是最花费资源的操作。为了最大限度地减少瓶颈并提高性能,我们必须对SQL查询进行优化。
以下是一个关于join操作的示例,用于模拟teacher和student两张表的关联查询:
EXPLAIN SELECT * FROM teacher JOIN student ON teacher.id = student.teacher_id WHERE student.gender = '男';
执行后的查询计划如下:
+----+-------------+--------+------------+------+---------------+--------+---------+--------------------------+------+----------+-------+ | ID | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+--------+------------+------+---------------+--------+---------+--------------------------+------+----------+-------+ | 1 | SIMPLE | student| NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where | | 1 | SIMPLE | teacher| NULL | ALL | PRIMARY | NULL | NULL | NULL | 2 | 100.00 | Using join buffer | +----+-------------+--------+------------+------+---------------+--------+---------+--------------------------+------+----------+-------+
在这个例子中,我们可以看到Extra列是Using join buffer,这意味着mysql需要对结果使用外部算法进行排序,并且JOIN缓冲区不足以处理查询的连接关系,因此需要使用外部排序。
为了避免这样的性能问题,我们可以创建适当的索引,例如对于student表中的teacher_id字段来说,可能会创建一个索引字符串类型。以下是一个为该字段创建索引的示例SQL语句:
ALTER TABLE student ADD INDEX (teacher_id);
然后,重新执行查询命令:
EXPLAIN SELECT * FROM teacher JOIN student ON teacher.id = student.teacher_id WHERE student.gender = '男';
执行计划如下:
+----+-------------+--------+------------+------+---------------+--------------+---------+--------------------------+------+----------+-------------+ | ID | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+--------+------------+------+---------------+--------------+---------+--------------------------+------+----------+-------------+ | 1 | SIMPLE | student| NULL | ref | teacher_id | teacher_id | 5 | const | 1 | 100.00 | Using index | | 1 | SIMPLE | teacher| NULL | ALL | PRIMARY | NULL | NULL | NULL | 2 | 100.00 | Using where | +----+-------------+--------+------------+------+---------------+--------------+---------+--------------------------+------+----------+-------------+
该查询执行计划的性能得到了大幅提升,type列现在变成了ref,说明我们已经使用了索引来优化查询。
四、查询优化实践
了解了mysql的explain命令的基本知识之后,我们现在来看一个实际的查询优化案例,其中使用了一些上述提到的优化技巧。
用例总结:假设我们有一个users表,其中存储了用户的姓名,年龄和性别信息。我们需要查询年龄在18岁以上的男性用户,并按照年龄降序,姓名升序排序,返回前10条数据。以下是这个查询的SQL语句及其执行计划:
EXPLAIN SELECT name,age,gender FROM users WHERE age > 18 AND gender = '男' ORDER BY age DESC,name ASC LIMIT 10;
执行计划如下:
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------+ | ID | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------+ | 1 | SIMPLE | users | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 10.00 | Using where; Using filesort | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------+
根据上面的执行计划结果,我们可以看到,mysql并没有使用任何索引,而是使用了WHERE条件进行查询,并使用了SORT排序算法。这样效率低下且需要更多的磁盘空间。
为了优化查询性能,我们可以考虑为age和gender字段创建索引,以下是一个为这些字段创建索引的示例SQL语句:
ALTER TABLE users ADD INDEX age_gender_idx(age, gender);
然后,重新执行查询命令:
EXPLAIN SELECT name,age,gender FROM users WHERE age > 18 AND gender = '男' ORDER BY age DESC,name ASC LIMIT 10;
执行计划如下:
+----+-------------+-------+------------+------+---------------+--------------+---------+------+------+----------+--------------------------+ | ID | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+--------------+---------+------+------+----------+--------------------------+ | 1 | SIMPLE | users | NULL | ref | age_gender_idx| age_gender_idx| 6 | const| 28 | 90.00 | Using where | +----+-------------+-------+------------+------+---------------+--------------+---------+------+------+----------+--------------------------+
这次查询的