MySQL性能优化与索引之:如何使用EXPLAIN
和SHOW STATUS
分析慢查询
大家好,今天我们来聊聊MySQL性能优化的一个重要方面:如何使用EXPLAIN
和SHOW STATUS
分析慢查询。 慢查询是数据库性能的头号大敌,它们不仅拖慢了单个请求的速度,还会消耗大量资源,影响整个系统的稳定性。而EXPLAIN
和SHOW STATUS
则是我们诊断慢查询问题的两大利器。
一、EXPLAIN
:查询执行计划分析
EXPLAIN
命令可以显示MySQL如何执行一个查询,它会返回查询的执行计划,包括MySQL选择的索引、连接方式、扫描行数等信息。通过分析EXPLAIN
的结果,我们可以了解查询的瓶颈所在,从而进行相应的优化。
1. EXPLAIN
的基本用法
在SQL查询语句前加上EXPLAIN
关键字即可使用,例如:
EXPLAIN SELECT * FROM users WHERE age > 25 AND city = 'Beijing';
执行后,MySQL会返回一个包含多列的结果集,每一行代表查询执行计划的一个步骤。下面我们将详细介绍这些列的含义。
2. EXPLAIN
结果列的含义
列名 | 含义 |
---|---|
id |
查询的唯一标识符。如果查询包含子查询,每个子查询都会有一个独立的id 。id 值越大,执行优先级越高。 |
select_type |
查询的类型,常见的类型包括:SIMPLE (简单查询,不包含子查询或UNION)、PRIMARY (最外层的SELECT语句)、SUBQUERY (子查询)、DERIVED (派生表,即FROM子句中的子查询)、UNION 、UNION RESULT 等。 |
table |
查询涉及的表名。 |
partitions |
查询涉及的分区信息。 |
type |
访问类型,表示MySQL如何查找表中的行,这是EXPLAIN 结果中最重要的列之一。常见的类型包括:system 、const 、eq_ref 、ref 、range 、index 、ALL 。 性能由好到差依次是:system > const > eq_ref > ref > range > index > ALL 。我们通常需要优化到ref 或range 级别。 |
possible_keys |
可能使用的索引。 |
key |
实际使用的索引。如果为NULL ,表示没有使用索引。 |
key_len |
索引的长度,单位是字节。长度越短,通常意味着性能越好。 |
ref |
使用哪个列或常数与索引比较。 |
rows |
MySQL估计需要扫描的行数。这个值越小,查询效率越高。 |
filtered |
过滤的百分比。表示经过条件过滤后,剩余的行占总行数的百分比。 |
Extra |
包含其他重要的信息,例如:Using index (表示使用了覆盖索引)、Using where (表示使用了WHERE子句过滤)、Using temporary (表示使用了临时表)、Using filesort (表示使用了文件排序)等。 |
3. 常见的type
类型详解
-
system
: 表只有一行记录(等于系统表),这是const
类型的一个特例,平时不会出现,可以忽略不计。 -
const
: 使用主键索引或唯一索引,且where条件中指定了常量值。因为只匹配一行数据,所以非常快。例如:EXPLAIN SELECT * FROM users WHERE id = 1; -- id是主键
-
eq_ref
: 使用主键索引或唯一索引,且where条件中使用了关联查询。这意味着对于前一个表中的每一行,都只能匹配到后一个表中的一行。通常出现在多表连接查询中。例如:EXPLAIN SELECT * FROM orders o JOIN users u ON o.user_id = u.id WHERE u.id = 1; -- user_id是orders表的外键,关联到users表的主键id
-
ref
: 使用普通索引,且where条件中使用了索引列。这意味着MySQL会使用索引来查找匹配的行,但可能会匹配到多行。例如:EXPLAIN SELECT * FROM users WHERE city = 'Beijing'; -- city是普通索引
-
range
: 使用索引进行范围扫描。通常出现在BETWEEN
、>
、<
、IN
等操作符的查询中。例如:EXPLAIN SELECT * FROM users WHERE age BETWEEN 20 AND 30; -- age是索引
-
index
: 扫描整个索引树。通常发生在SELECT
语句只包含索引列,或者WHERE
条件中使用了索引列但MySQL认为全表扫描更快的情况下。例如:EXPLAIN SELECT age FROM users; -- age是索引
-
ALL
: 全表扫描。这是最慢的访问类型,意味着MySQL会扫描整个表来查找匹配的行。应该尽量避免出现ALL
类型的查询。
4. 常见的Extra
信息详解
-
Using index
: 使用了覆盖索引。这意味着查询只需要从索引中获取数据,而不需要回表查询,可以大大提高查询效率。例如:EXPLAIN SELECT age FROM users WHERE age > 25; -- age是索引
-
Using where
: 使用了WHERE子句过滤。这意味着MySQL在存储引擎层获取到数据后,还需要使用WHERE子句进行过滤。 -
Using temporary
: 使用了临时表。这意味着MySQL需要创建一个临时表来存储中间结果,这通常发生在ORDER BY
、GROUP BY
等操作中。使用临时表会降低查询效率,应该尽量避免。 -
Using filesort
: 使用了文件排序。这意味着MySQL无法使用索引进行排序,需要将数据加载到内存或磁盘中进行排序。文件排序的效率很低,应该尽量避免。
5. EXPLAIN
示例分析
假设我们有一个users
表,包含以下字段:
id
(INT, PRIMARY KEY)name
(VARCHAR(255))age
(INT)city
(VARCHAR(255))
并且我们在age
和city
字段上创建了索引:
CREATE INDEX idx_age ON users (age);
CREATE INDEX idx_city ON users (city);
现在我们执行以下查询:
EXPLAIN SELECT * FROM users WHERE age > 25 AND city = 'Beijing';
EXPLAIN
的结果可能如下所示:
+----+-------------+-------+------------+-------+---------------------+---------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------------+---------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | users | NULL | range | idx_age,idx_city | idx_age | 4 | NULL | 1000 | 10.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------------+---------+---------+------+------+----------+-----------------------+
+----+-------------+-------+------------+-------+---------------------+---------+---------+------+------+----------+-----------------------+
分析结果:
type
是range
,表示使用了索引进行范围扫描。possible_keys
是idx_age,idx_city
,表示MySQL可以选择使用idx_age
或idx_city
索引。key
是idx_age
,表示MySQL实际使用了idx_age
索引。rows
是1000
,表示MySQL估计需要扫描1000行数据。Extra
是Using index condition
,表示MySQL使用了索引条件下推(Index Condition Pushdown, ICP)技术,可以减少回表查询的次数。
优化方向:
从上面的EXPLAIN
结果可以看出,MySQL选择了idx_age
索引,但并没有使用idx_city
索引。这可能是因为age > 25
的范围比较大,导致MySQL认为使用idx_age
索引已经足够了。 为了进一步优化查询,我们可以创建一个组合索引,将age
和city
字段组合在一起:
CREATE INDEX idx_age_city ON users (age, city);
然后再次执行EXPLAIN
:
EXPLAIN SELECT * FROM users WHERE age > 25 AND city = 'Beijing';
EXPLAIN
的结果可能如下所示:
+----+-------------+-------+------------+-------+---------------------+------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------------+------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | users | NULL | range | idx_age,idx_city,idx_age_city | idx_age_city | 9 | NULL | 100 | 100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------------+------------+---------+------+------+----------+-----------------------+
分析结果:
key
是idx_age_city
,表示MySQL实际使用了idx_age_city
组合索引。rows
是100
,表示MySQL估计需要扫描100行数据,比之前的1000行少了很多。
通过创建组合索引,我们成功地减少了MySQL需要扫描的行数,提高了查询效率。
6. 注意事项
EXPLAIN
只能显示查询的执行计划,并不能真正执行查询。因此,EXPLAIN
的结果只能作为参考,不能完全依赖。EXPLAIN
的结果可能会受到MySQL版本、数据量、索引状态等因素的影响。因此,在不同的环境下,EXPLAIN
的结果可能会有所不同。- 在优化查询时,应该结合实际情况,综合考虑各种因素,才能找到最佳的优化方案。
二、SHOW STATUS
:服务器状态变量分析
SHOW STATUS
命令可以显示MySQL服务器的各种状态变量,包括查询次数、连接次数、线程数、缓存命中率等信息。通过分析SHOW STATUS
的结果,我们可以了解MySQL服务器的整体运行状况,从而发现潜在的性能问题。
1. SHOW STATUS
的基本用法
SHOW STATUS
命令可以显示所有状态变量,也可以显示指定的状态变量。例如:
SHOW STATUS; -- 显示所有状态变量
SHOW STATUS LIKE 'Connections'; -- 显示Connections状态变量
2. 重要的状态变量
状态变量名 | 含义 |
---|---|
Uptime |
服务器运行时间,单位是秒。 |
Connections |
连接服务器的次数。 |
Slow_queries |
慢查询的次数。 |
Questions |
服务器接收到的查询次数。 |
Threads_connected |
当前连接的线程数。 |
Threads_created |
创建的线程数。 |
Threads_cached |
缓存的线程数。 |
Qcache_hits |
查询缓存的命中次数。 |
Qcache_inserts |
查询缓存的插入次数。 |
Qcache_not_cached |
没有被缓存的查询次数。 |
Key_read_requests |
索引读取请求次数。 |
Key_reads |
磁盘读取索引次数。 |
Key_cache_miss_rate |
索引缓存的缺失率,计算公式为:Key_reads / Key_read_requests 。 如果这个值过高,说明索引缓存不足,需要增加key_buffer_size 配置。 |
Innodb_buffer_pool_reads |
InnoDB缓冲池读取的次数。 |
Innodb_buffer_pool_read_requests |
InnoDB缓冲池读取请求的次数。 |
Innodb_buffer_pool_miss_rate |
InnoDB缓冲池的缺失率,计算公式为:Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests 。 如果这个值过高,说明InnoDB缓冲池不足,需要增加innodb_buffer_pool_size 配置。 |
Handler_read_rnd_next |
读取下一行数据的请求次数。如果这个值过高,说明存在大量的全表扫描,需要优化查询或增加索引。 |
Created_tmp_disk_tables |
创建磁盘临时表的次数。如果这个值过高,说明存在大量的ORDER BY 、GROUP BY 等操作,需要优化查询或增加索引。 |
Created_tmp_tables |
创建内存临时表的次数。如果这个值过高,也说明存在大量的ORDER BY 、GROUP BY 等操作,需要优化查询或增加索引。 |
3. 如何使用SHOW STATUS
分析性能问题
- 查看慢查询次数: 通过
SHOW STATUS LIKE 'Slow_queries';
可以查看慢查询的次数。如果慢查询次数过多,说明系统存在性能问题,需要进一步分析。 - 查看连接数: 通过
SHOW STATUS LIKE 'Connections';
和SHOW STATUS LIKE 'Threads_connected';
可以查看连接服务器的次数和当前连接的线程数。如果连接数过高,可能会导致服务器资源耗尽,影响性能。 - 查看缓存命中率: 通过
SHOW STATUS LIKE 'Qcache_hits';
、SHOW STATUS LIKE 'Qcache_inserts';
和SHOW STATUS LIKE 'Qcache_not_cached';
可以查看查询缓存的命中率。如果缓存命中率过低,说明查询缓存没有起到应有的作用,可以考虑禁用查询缓存或优化查询。 - 查看索引使用情况: 通过
SHOW STATUS LIKE 'Key_read_requests';
和SHOW STATUS LIKE 'Key_reads';
可以查看索引的使用情况。如果Key_reads
的值很高,说明存在大量的磁盘读取索引操作,需要优化查询或增加索引。 - 查看临时表使用情况: 通过
SHOW STATUS LIKE 'Created_tmp_disk_tables';
和SHOW STATUS LIKE 'Created_tmp_tables';
可以查看临时表的使用情况。如果临时表使用过多,说明存在大量的ORDER BY
、GROUP BY
等操作,需要优化查询或增加索引。
4. SHOW STATUS
示例分析
假设我们执行SHOW STATUS LIKE 'Slow_queries';
得到以下结果:
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Slow_queries | 100 |
+---------------+-------+
这表示系统中有100个慢查询。接下来,我们可以通过以下步骤来分析慢查询:
-
开启慢查询日志: 修改MySQL配置文件(例如
my.cnf
),开启慢查询日志,并设置慢查询阈值(例如1秒)。slow_query_log = 1 slow_query_log_file = /var/log/mysql/mysql-slow.log long_query_time = 1
-
分析慢查询日志: 使用
mysqldumpslow
工具或第三方工具(例如pt-query-digest
)分析慢查询日志,找出执行频率最高的慢查询。 -
使用
EXPLAIN
分析慢查询: 对找出的慢查询使用EXPLAIN
命令,分析查询的执行计划,找出瓶颈所在。 -
优化查询: 根据
EXPLAIN
的结果,优化查询,例如增加索引、重写SQL语句等。 -
验证优化效果: 优化后,再次执行慢查询,并使用
EXPLAIN
命令验证优化效果。如果查询速度明显提升,说明优化成功。
三、 结合使用EXPLAIN
和SHOW STATUS
EXPLAIN
和SHOW STATUS
是两个互补的工具,我们可以结合使用它们来分析慢查询问题。
- 使用
SHOW STATUS
监控系统状态,发现潜在的性能问题。 - 如果发现慢查询次数过多,开启慢查询日志,并分析慢查询日志,找出执行频率最高的慢查询。
- 使用
EXPLAIN
分析慢查询的执行计划,找出瓶颈所在。 - 根据
EXPLAIN
的结果,优化查询,例如增加索引、重写SQL语句等。 - 优化后,再次使用
EXPLAIN
验证优化效果。 - 持续使用
SHOW STATUS
监控系统状态,确保优化效果持久有效。
四、优化查询的常用方法
- 增加索引: 索引是提高查询效率最常用的方法。应该根据查询条件,在经常被查询的列上增加索引。
- 优化SQL语句: 应该尽量避免使用复杂的SQL语句,例如子查询、JOIN查询等。可以使用简单的SQL语句代替复杂的SQL语句,或者使用缓存来减少查询次数。
- 优化表结构: 应该尽量避免使用过大的表,可以将大表拆分成小表。可以使用合适的数据类型,减少存储空间。
- 调整MySQL配置: 可以根据实际情况,调整MySQL的配置参数,例如
key_buffer_size
、innodb_buffer_pool_size
等。
五、总结
EXPLAIN
和SHOW STATUS
是MySQL性能优化的两大利器,通过熟练掌握它们的使用方法,我们可以快速定位慢查询问题,并采取相应的优化措施,从而提高数据库的性能和稳定性。记住,优化是一个持续的过程,需要不断地学习和实践。
六、对症下药,持续优化
EXPLAIN
帮助我们理解查询执行方式,SHOW STATUS
揭示服务器运行状况。结合两者,找出性能瓶颈,持续优化才能获得更好的数据库性能。