如何获取查询慢的sql
- 通过用户反馈获取
- 通过慢查日志获取
- 实时获取
慢查日志
慢查日志需要注意这样几个参数:
slow_query_log
:启动/停止记录慢查日志slow_query_log_file
:指定慢查日志的存储路径及文件long_query_time
:指定慢查日志收集SQL的执行时间阈值long_queries_not_using_indexes
:是否记录未使用索引的sql
可以看到,我云服务器上的慢查询日志是未开启的,使用set global
开启。
慢查日志暂时就介绍到这里,可以使用常用的慢查日志分析工具帮助分析。
实时获取
SELECT id,‘user‘,‘host‘,DB,command,‘time‘,state,info
FROM information_schema.PROCESSLIST
WHERE TIME> =60
当前服务器,执行 时间超过60s的sql
通过脚本周期性地执行这句,就可以实时发现执行慢的sql
慢查询基础:优化数据访问
如果把查询看作一个任务,那么它由一系列子任务组成,每个子任务都会消耗一定的时间。如果要优化查询,实际上是要优化其子任务,要么消除一些子任务,要么减少子任务的执行次数。
查询性能低下最基本的原因是访问的数据太多,某些查询可能不可避免的需要筛选大量数据,但并不十分常见。大部分性能低下的查询都可以通过减少访问的数据量的方式进行优化,一般是通过下面两个步骤:
- 确认应用程序是否在检索大量超过需要的数据(访问了太多的行或者列)。
- 确认Mysql服务器是否在分析大量超过需要的行。
第一个步骤,需要注意自己写的sql语句。
- 如果前台只需要显示15条数据,而你的查询结果集返回了100条,则要想想是否真有必要这样干了,最好使用LIMIT来限制查询的条数。
- 尽量避免使用SELECT * , 也许你并不需要所有的列,但获取所有的列将会造成覆盖索引这样的优化手段失效,也会增加磁盘I/O、内存和CPU的开销等,所以基于这种情况,尽量使用SELECT t.id, t.name … 这种查询具体字段的SQL。
详细说说第二个步骤。
对于Mysql来说,最简单的衡量查询开销的三个指标:
- 响应时间
- 扫描行数
- 返回行数
响应时间包括:服务时间和排队时间。服务时间是数据库处理这个查询真正花费的时间,而排队时间则是等待某些资源(如I/O或者锁)的时间。
这三个值需要根据实际经验来判断响应时间是否是一个合理的值,通过explain
来查看一个查询语句的各种属性。
比如:
explain
select ecm_order_id,resource
from vem_delivery
where ecm_order_id=201800013223001
EXPLAIN列的解释:
- table:显示这一行的数据是关于哪张表的
- type:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、index和ALL
- possible_keys:显示可能应用在这张表中的索引。如果为空,没有可能的索引。可以为相关的域从WHERE语句中选择一个合适的语句
- key: 实际使用的索引。如果为NULL,则没有使用索引。很少的情况下,MYSQL会选择优化不足的索引。这种情况下,可以在SELECT语句中使用USE INDEX(indexname)来强制使用一个索引或者用IGNORE INDEX(indexname)来强制MYSQL忽略索引
- key_len:使用的索引的长度。在不损失精确性的情况下,长度越短越好
- ref:显示索引的哪一列被使用了,如果可能的话,是一个常数
- rows:MYSQL认为必须检查的用来返回请求数据的行数
重构查询方式
有些时候我们需要重写查询以获取更好的性能,尽管得到的结果可能不同,也许最终程序的代码也会和查询一起被改。
把一个耗时的复杂查询分解成多个简单的查询。
平时我们更倡导用尽可能少的查询做尽可能多的事情,这样可以减少网络通信开销,能减少查询解析和优化的步骤,以及代码上似乎更优雅。 但是在MySql中,MySql被设计成可以很高效地连接和断开服务器,而且能很快地响应精简的查询。在现代网络下,MySql在一般的服务器上每秒钟可以处理50000个查询。因此,对于一些耗时的复杂查询,可以通过分解查询以得到更高的效率。
比如:分解联接,把一个多表联接分解成多个单表查询,然后在应用程序端实现联接。
例如有如下的一个连接查询:
SELECT * FROM tag JOIN tag_post ON tag.id = tag_post.tag_id WHERE tag.title = ‘test’;
分解成两个查询:
SELECT * FROM tag WHERE tag.title = ‘test’; – 假设返回id有 (10,11,12,13,14,15);
SELECT * FROM tag_post WHERE tag_id IN (10,11,12,13,14,15);
这样分解查询,看似浪费,但其针对一些耗时的多表联接能带来很好的性能提升:
- 缓存的性能更高:上面的查询已经被缓存起来,下次再查询tag.title = ‘test’,则会直接从缓存中取出;第二条IN操作,下次查询(11,12,14, 20,25),对于11,12,14则直接从缓存中取出,只去读取20,25。如果一个表经常改变,分解联接可以减少缓存失效的次数。
- 可以减少多余的行访问,联接操作,每从tag表中检查一行,就会去tag_post中去检查。
- 优化
not in
和<>
查询
直接看下面的例子比较直观:
Mysql内部机制
查询主要分五步:
- 客户端发送SQL给服务器
- 服务器检查缓存是否命中,如果命中,检查权限后返回数据;否则进入到第三步
- 服务器对SQL解析、预处理,再由优化器生成对应的执行计划
- 根据执行计划,调用存储引擎API来查询数据
- 将数据结果返回给客户端
其实第2-5步是可能对查询的速度产生影响的。
对于第二步查询缓存,我们之前写的DDBS 分布式DB与Cache一致性中提到过,如果有写入数据,则会对缓存造成影响,比如写入时对缓存加锁。所以,在 读写频繁的系统使用查询缓存可能会降低查询处理的效率,在这种情况下,建议通过query_catch_type
关闭查询缓存。
其他优化,比如查询优化器内部机制不做介绍了。
如何确定查询各个阶段所消耗的时间呢?
通过使用profile即可。
- set profiling=1;
- 执行查询语句。
- show profiles;
- show profile for query 1; 查询每个阶段所消耗的时间